From ef83eee2d192684d7efc9b154e6e4632aeba2091 Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Mon, 26 Aug 2024 22:38:44 +0500 Subject: [PATCH] Refactor enclave server --- packages/server/.cargo/config.toml | 3 + packages/server/Cargo.lock | 11 + packages/server/src/bin/cli.rs | 358 +---- packages/server/src/bin/enclave_server.rs | 1238 +---------------- packages/server/src/bin/start_rounds.rs | 4 +- packages/server/src/cli/auth.rs | 38 + packages/server/src/cli/mod.rs | 90 ++ packages/server/src/cli/voting.rs | 211 +++ .../server/src/enclave_server/database.rs | 66 + packages/server/src/enclave_server/mod.rs | 23 + packages/server/src/enclave_server/models.rs | 239 ++++ .../src/enclave_server/routes/ciphernode.rs | 365 +++++ .../server/src/enclave_server/routes/index.rs | 102 ++ .../server/src/enclave_server/routes/mod.rs | 15 + .../src/enclave_server/routes/rounds.rs | 243 ++++ .../server/src/enclave_server/routes/state.rs | 148 ++ .../src/enclave_server/routes/voting.rs | 144 ++ packages/server/src/lib.rs | 3 + packages/server/src/main.rs | 1 - packages/server/src/{bin => }/util.rs | 2 +- 20 files changed, 1709 insertions(+), 1595 deletions(-) create mode 100644 packages/server/.cargo/config.toml create mode 100644 packages/server/src/cli/auth.rs create mode 100644 packages/server/src/cli/mod.rs create mode 100644 packages/server/src/cli/voting.rs create mode 100644 packages/server/src/enclave_server/database.rs create mode 100644 packages/server/src/enclave_server/mod.rs create mode 100644 packages/server/src/enclave_server/models.rs create mode 100644 packages/server/src/enclave_server/routes/ciphernode.rs create mode 100644 packages/server/src/enclave_server/routes/index.rs create mode 100644 packages/server/src/enclave_server/routes/mod.rs create mode 100644 packages/server/src/enclave_server/routes/rounds.rs create mode 100644 packages/server/src/enclave_server/routes/state.rs create mode 100644 packages/server/src/enclave_server/routes/voting.rs create mode 100644 packages/server/src/lib.rs rename packages/server/src/{bin => }/util.rs (98%) diff --git a/packages/server/.cargo/config.toml b/packages/server/.cargo/config.toml new file mode 100644 index 0000000..0c51459 --- /dev/null +++ b/packages/server/.cargo/config.toml @@ -0,0 +1,3 @@ +[env] +INFURAKEY = "default val" +PRIVATEKEY = "default val" \ No newline at end of file diff --git a/packages/server/Cargo.lock b/packages/server/Cargo.lock index 822ad47..6ecccb9 100644 --- a/packages/server/Cargo.lock +++ b/packages/server/Cargo.lock @@ -1005,6 +1005,7 @@ dependencies = [ "risc0-zkp", "risc0-zkvm", "serde", + "zk-kit-imt", ] [[package]] @@ -6343,6 +6344,16 @@ dependencies = [ "zstd", ] +[[package]] +name = "zk-kit-imt" +version = "0.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4bec228e2acafec7bd22c9a0a5e0c5e2a6d0c17df69f5ad11c24ce6dc6356c6" +dependencies = [ + "hex", + "tiny-keccak", +] + [[package]] name = "zstd" version = "0.11.2+zstd.1.5.2" diff --git a/packages/server/src/bin/cli.rs b/packages/server/src/bin/cli.rs index 6453160..049ae4c 100644 --- a/packages/server/src/bin/cli.rs +++ b/packages/server/src/bin/cli.rs @@ -1,357 +1,5 @@ -mod util; +use rfv::cli::run_cli; -use dialoguer::{theme::ColorfulTheme, Input, FuzzySelect}; -use std::{thread, time, env}; -use serde::{Deserialize, Serialize}; -use std::fs::File; -use std::io::Read; - -use fhe::{ - bfv::{BfvParametersBuilder, Encoding, Plaintext, PublicKey}, -}; -use fhe_traits::{FheEncoder, FheEncrypter, Serialize as FheSerialize, DeserializeParametrized}; -use rand::{thread_rng}; -use util::timeit::{timeit}; - -use hyper::Request; -use hyper::Method; -use hyper_tls::HttpsConnector; -use hyper_util::{client::legacy::Client as HyperClient, rt::TokioExecutor}; -use bytes::Bytes; - -use http_body_util::Empty; -use http_body_util::BodyExt; -use tokio::io::{AsyncWriteExt as _, self}; - -use hmac::{Hmac, Mac}; -use jwt::SignWithKey; -use sha2::Sha256; -use std::collections::BTreeMap; - - -#[derive(Debug, Deserialize, Serialize)] -struct JsonResponse { - response: String -} - -#[derive(Debug, Deserialize, Serialize)] -struct JsonResponseTxHash { - response: String, - tx_hash: String, -} - -#[derive(Debug, Deserialize, Serialize)] -struct JsonRequestGetRounds { - response: String, -} - -#[derive(Debug, Deserialize, Serialize)] -struct RoundCount { - round_count: u32, -} - -#[derive(Debug, Deserialize, Serialize)] -struct JsonRequest { - response: String, - pk_share: u32, - id: u32, - round_id: u32, -} - -#[derive(Debug, Deserialize, Serialize)] -struct CrispConfig { - round_id: u32, - poll_length: u32, - chain_id: u32, - voting_address: String, - ciphernode_count: u32, - enclave_address: String, - authentication_id: String, -} - -#[derive(Debug, Deserialize, Serialize)] -struct PKRequest { - round_id: u32, - pk_bytes: Vec, -} - -#[derive(Debug, Deserialize, Serialize)] -struct EncryptedVote { - round_id: u32, - enc_vote_bytes: Vec, - postId: String, -} - -#[derive(Debug, Deserialize, Serialize)] -struct AuthenticationLogin { - postId: String, -} - -#[derive(Debug, Deserialize, Serialize)] -struct AuthenticationResponse { - response: String, - jwt_token: String, -} - -#[tokio::main] -async fn main() -> Result<(), Box> { - - let https = HttpsConnector::new(); - //let client = HyperClient::builder(TokioExecutor::new()).build::<_, Empty>(https); - let client_get = HyperClient::builder(TokioExecutor::new()).build::<_, Empty>(https.clone()); - let client = HyperClient::builder(TokioExecutor::new()).build::<_, String>(https); - let mut auth_res = AuthenticationResponse { - response: "".to_string(), - jwt_token: "".to_string(), - }; - - print!("{esc}[2J{esc}[1;1H", esc = 27 as char); - let selections = &[ - "CRISP: Voting Protocol (ETH)", - "More Coming Soon!" - ]; - - let selections_2 = &[ - "Initialize new CRISP round.", - "Continue Existing CRISP round." - ]; - - let selections_3 = &[ - "Abstain.", - "Vote yes.", - "Vote no." - ]; - - let selection_1 = FuzzySelect::with_theme(&ColorfulTheme::default()) - .with_prompt("Enclave (EEEE): Please choose the private execution environment you would like to run!") - .default(0) - .items(&selections[..]) - .interact() - .unwrap(); - - if selection_1 == 0 { - print!("{esc}[2J{esc}[1;1H", esc = 27 as char); - //println!("Encrypted Protocol Selected {}!", selections[selection_1]); - let selection_2 = FuzzySelect::with_theme(&ColorfulTheme::default()) - .with_prompt("Create a new CRISP round or particpate in an existing round.") - .default(0) - .items(&selections_2[..]) - .interact() - .unwrap(); - - if selection_2 == 0 { - print!("{esc}[2J{esc}[1;1H", esc = 27 as char); - println!("Starting new CRISP round!"); - // let input_token: String = Input::with_theme(&ColorfulTheme::default()) - // .with_prompt("Enter Proposal Registration Token") - // .interact_text() - // .unwrap(); - print!("{esc}[2J{esc}[1;1H", esc = 27 as char); - println!("Reading proposal details from config."); - let path = env::current_dir().unwrap(); - let mut pathst = path.display().to_string(); - pathst.push_str("/example_config.json"); - let mut file = File::open(pathst).unwrap(); - let mut data = String::new(); - file.read_to_string(&mut data).unwrap(); - let config: CrispConfig = serde_json::from_str(&data).expect("JSON was not well-formatted"); - println!("round id: {:?}", config.round_id); // get new round id from current id in server - println!("poll length {:?}", config.poll_length); - println!("chain id: {:?}", config.chain_id); - println!("voting contract: {:?}", config.voting_address); - println!("ciphernode count: {:?}", config.ciphernode_count); - - println!("Initializing Keyshare nodes..."); - - let response_id = JsonRequestGetRounds { response: "Test".to_string() }; - let _out = serde_json::to_string(&response_id).unwrap(); - let mut url_id = config.enclave_address.clone(); - url_id.push_str("/get_rounds"); - - //let token = Authorization::bearer("some-opaque-token").unwrap(); - //println!("bearer token {:?}", token.token()); - //todo: add auth field to config file to get bearer token - let req = Request::builder() - .method(Method::GET) - .uri(url_id) - .body(Empty::::new())?; - - let resp = client_get.request(req).await?; - - println!("Response status: {}", resp.status()); - - let body_bytes = resp.collect().await?.to_bytes(); - let body_str = String::from_utf8(body_bytes.to_vec()).unwrap(); - let count: RoundCount = serde_json::from_str(&body_str).expect("JSON was not well-formatted"); - println!("Server Round Count: {:?}", count.round_count); - - // TODO: get secret from env var - // let key: Hmac = Hmac::new_from_slice(b"some-secret")?; - // let mut claims = BTreeMap::new(); - // claims.insert("postId", config.authentication); - // let mut bearer_str = "Bearer ".to_string(); - // let token_str = claims.sign_with_key(&key)?; - // bearer_str.push_str(&token_str); - // println!("{:?}", bearer_str); - - let round_id = count.round_count + 1; - let response = CrispConfig { - round_id: round_id, - poll_length: config.poll_length, - chain_id: config.chain_id, - voting_address: config.voting_address, - ciphernode_count: config.ciphernode_count, - enclave_address: config.enclave_address.clone(), - authentication_id: config.authentication_id.clone(), - }; - let out = serde_json::to_string(&response).unwrap(); - let mut url = config.enclave_address.clone(); - url.push_str("/init_crisp_round"); - let req = Request::builder() - .header("authorization", "Bearer fpKL54jvWmEGVoRdCNjG") - .method(Method::POST) - .uri(url) - .body(out)?; - - let mut resp = client.request(req).await?; - - println!("Response status: {}", resp.status()); - - while let Some(next) = resp.frame().await { - let frame = next?; - if let Some(chunk) = frame.data_ref() { - io::stdout().write_all(chunk).await?; - } - } - println!("Round Initialized."); - println!("Gathering Keyshare nodes for execution environment..."); - let three_seconds = time::Duration::from_millis(1000); - thread::sleep(three_seconds); - println!("\nYou can now vote Encrypted with Round ID: {:?}", round_id); - - } - if selection_2 == 1 { - print!("{esc}[2J{esc}[1;1H", esc = 27 as char); - let input_crisp_id: u32 = Input::with_theme(&ColorfulTheme::default()) - .with_prompt("Enter CRISP round ID.") - .interact_text() - .unwrap(); - let path = env::current_dir().unwrap(); - let mut pathst = path.display().to_string(); - pathst.push_str("/example_config.json"); - let mut file = File::open(pathst).unwrap(); - let mut data = String::new(); - file.read_to_string(&mut data).unwrap(); - let config: CrispConfig = serde_json::from_str(&data).expect("JSON was not well-formatted"); - println!("Voting state Initialized"); - - // get authentication token - let user = AuthenticationLogin { - postId: config.authentication_id.clone(), - }; - - let out = serde_json::to_string(&user).unwrap(); - let mut url = config.enclave_address.clone(); - url.push_str("/authentication_login"); - let req = Request::builder() - .method(Method::POST) - .uri(url) - .body(out)?; - - let mut resp = client.request(req).await?; - let body_bytes = resp.collect().await?.to_bytes(); - let body_str = String::from_utf8(body_bytes.to_vec()).unwrap(); - auth_res = serde_json::from_str(&body_str).expect("JSON was not well-formatted"); - println!("Authentication response {:?}", auth_res); - - // get public encrypt key - let v: Vec = vec! [0]; - let response_pk = PKRequest { round_id: input_crisp_id, pk_bytes: v }; - let out = serde_json::to_string(&response_pk).unwrap(); - let mut url = config.enclave_address.clone(); - url.push_str("/get_pk_by_round"); - let req = Request::builder() - .method(Method::POST) - .uri(url) - .body(out)?; - - let resp = client.request(req).await?; - - println!("Response status: {}", resp.status()); - - let body_bytes = resp.collect().await?.to_bytes(); - let body_str = String::from_utf8(body_bytes.to_vec()).unwrap(); - let pk_res: PKRequest = serde_json::from_str(&body_str).expect("JSON was not well-formatted"); - println!("Shared Public Key for CRISP round {:?} collected.", pk_res.round_id); - - let degree = 4096; - let plaintext_modulus: u64 = 4096; - let moduli = vec![0xffffee001, 0xffffc4001, 0x1ffffe0001]; - // Let's generate the BFV parameters structure. - let params = timeit!( - "Parameters generation", - BfvParametersBuilder::new() - .set_degree(degree) - .set_plaintext_modulus(plaintext_modulus) - .set_moduli(&moduli) - .build_arc()? - ); - let pk_deserialized = PublicKey::from_bytes(&pk_res.pk_bytes, ¶ms).unwrap(); - // todo: validate that this user can vote - let selection_3 = FuzzySelect::with_theme(&ColorfulTheme::default()) - .with_prompt("Please select your voting option.") - .default(0) - .items(&selections_3[..]) - .interact() - .unwrap(); - - let mut vote_choice: u64 = 0; - if selection_3 == 0 { - println!("Exiting voting system. You may choose to vote later."); - vote_choice = 0; - } - if selection_3 == 1 { - vote_choice = 1; - } - if selection_3 == 2 { - vote_choice = 0; - } - println!("Encrypting vote."); - let votes: Vec = [vote_choice].to_vec(); - let pt = Plaintext::try_encode(&[votes[0]], Encoding::poly(), ¶ms)?; - let ct = pk_deserialized.try_encrypt(&pt, &mut thread_rng())?; - println!("Vote encrypted."); - println!("Calling voting contract with encrypted vote."); - - let request_contract = EncryptedVote { - round_id: input_crisp_id, - enc_vote_bytes: ct.to_bytes(), - postId: auth_res.jwt_token, - }; - let out = serde_json::to_string(&request_contract).unwrap(); - let mut url = config.enclave_address.clone(); - url.push_str("/broadcast_enc_vote"); - let req = Request::builder() - .method(Method::POST) - .uri(url) - .body(out)?; - - let resp = client.request(req).await?; - - println!("Response status: {}", resp.status()); - - let body_bytes = resp.collect().await?.to_bytes(); - let body_str = String::from_utf8(body_bytes.to_vec()).unwrap(); - let contract_res: JsonResponseTxHash = serde_json::from_str(&body_str).expect("JSON was not well-formatted"); - println!("Contract call: {:?}", contract_res.response); - println!("TxHash is {:?}", contract_res.tx_hash); - } - - } - if selection_1 == 1 { - println!("Check back soon!"); - std::process::exit(1); - } - - Ok(()) +fn main() -> Result<(), Box> { + run_cli() } diff --git a/packages/server/src/bin/enclave_server.rs b/packages/server/src/bin/enclave_server.rs index 5123f9b..fee968a 100644 --- a/packages/server/src/bin/enclave_server.rs +++ b/packages/server/src/bin/enclave_server.rs @@ -1,1237 +1,5 @@ -mod util; +use rfv::enclave_server::start_server; -use std::{env, sync::Arc, str}; -use chrono::{Utc}; -use fhe::{ - bfv::{BfvParametersBuilder, PublicKey}, - mbfv::{AggregateIter, CommonRandomPoly, PublicKeyShare}, -}; -use fhe_traits::{Serialize as FheSerialize}; -use rand::{Rng, thread_rng}; -use util::timeit::{timeit}; -use serde::{Deserialize, Serialize}; - -use iron::prelude::*; -use iron::status; -use iron::mime::Mime; -use iron::Chain; -use iron::headers::{Header, HeaderFormat, HeaderFormatter, Bearer}; -use router::Router; -use std::io::Read; -use iron_cors::CorsMiddleware; - -use ethers::{ - prelude::{abigen}, - providers::{Http, Provider, Middleware}, - middleware::{SignerMiddleware, MiddlewareBuilder}, - signers::{LocalWallet, Signer}, - types::{Address, Bytes, TxHash, U64, BlockNumber}, -}; - -use sled::Db; -use once_cell::sync::Lazy; - -use hmac::{Hmac, Mac}; -use jwt::{ VerifyWithKey, SignWithKey }; -use sha2::Sha256; -use std::collections::BTreeMap; - -static GLOBAL_DB: Lazy = Lazy::new(|| { - let pathdb = env::current_dir().unwrap(); - let mut pathdbst = pathdb.display().to_string(); - pathdbst.push_str("/database/enclave_server"); - sled::open(pathdbst.clone()).unwrap() -}); - -//static open_db: Database = Database::new(); - -// static pathdb: String = env::current_dir().unwrap(); -// static mut pathdbst: String = pathdb.display().to_string(); -// pathdbst.push_str("/database"); -// static db = sled::open(pathdbst.clone()).unwrap(); -//static db: Db = sled::open("/home/ubuntu/guild/CRISP/packages/rust/database").unwrap(); - -// pick a string at random -fn pick_response() -> String { - "Test".to_string() -} - -#[derive(Debug, Deserialize, Serialize)] -struct JsonResponse { - response: String -} - -#[derive(Debug, Deserialize, Serialize)] -struct RegisterNodeResponse { - response: String, - node_index: u32, -} - -#[derive(Debug, Deserialize, Serialize)] -struct JsonResponseTxHash { - response: String, - tx_hash: String, -} - -#[derive(Debug, Deserialize, Serialize)] -struct JsonRequest { - response: String, - pk_share: Vec, - id: u32, - round_id: u32, -} - -#[derive(Debug, Deserialize, Serialize)] -struct CrispConfig { - round_id: u32, - poll_length: u32, - chain_id: u32, - voting_address: String, - ciphernode_count: u32, - enclave_address: String, - authentication_id: String, -} - -#[derive(Debug, Deserialize, Serialize)] -struct RoundCount { - round_count: u32, -} - -#[derive(Debug, Deserialize, Serialize)] -struct PKShareCount { - round_id: u32, - share_id: u32, -} - -#[derive(Debug, Deserialize, Serialize)] -struct PKRequest { - round_id: u32, - pk_bytes: Vec, -} - -#[derive(Debug, Deserialize, Serialize)] -struct CRPRequest { - round_id: u32, - crp_bytes: Vec, -} - -#[derive(Debug, Deserialize, Serialize)] -struct TimestampRequest { - round_id: u32, - timestamp: i64, -} - -#[derive(Debug, Deserialize, Serialize)] -struct PollLengthRequest { - round_id: u32, - poll_length: u32, -} - -#[derive(Debug, Deserialize, Serialize)] -struct VoteCountRequest { - round_id: u32, - vote_count: u32, -} - -#[derive(Debug, Deserialize, Serialize)] -struct SKSShareRequest { - response: String, - sks_share: Vec, - index: u32, - round_id: u32, -} - -#[derive(Debug, Deserialize, Serialize)] -struct EncryptedVote { - round_id: u32, - enc_vote_bytes: Vec, - postId: String, -} - -#[derive(Debug, Deserialize, Serialize)] -struct GetRoundRequest { - round_id: u32, -} - -#[derive(Debug, Deserialize, Serialize)] -struct GetEmojisRequest { - round_id: u32, - emojis: [String; 2], -} - -#[derive(Debug, Deserialize, Serialize)] -struct SKSSharePoll { - response: String, - round_id: u32, - ciphernode_count: u32, //TODO: dont need this -} - -#[derive(Debug, Deserialize, Serialize)] -struct SKSShareResponse { - response: String, - round_id: u32, - sks_shares: Vec>, -} - -#[derive(Debug, Deserialize, Serialize)] -struct ReportTallyRequest { - round_id: u32, - option_1: u32, - option_2: u32, -} - -#[derive(Debug, Deserialize, Serialize)] -struct WebResultRequest { - round_id: u32, - option_1_tally: u32, - option_2_tally: u32, - total_votes: u32, - option_1_emoji: String, - option_2_emoji: String, - end_time: i64 -} - -#[derive(Debug, Deserialize, Serialize)] -struct AllWebStates { - states: Vec, -} - -#[derive(Debug, Deserialize, Serialize)] -struct StateWeb { - id: u32, - status: String, - poll_length: u32, - voting_address: String, - chain_id: u32, - ciphernode_count: u32, - pk_share_count: u32, - sks_share_count: u32, - vote_count: u32, - start_time: i64, - ciphernode_total: u32, - emojis: [String; 2], -} - -#[derive(Debug, Deserialize, Serialize)] -struct StateLite { - id: u32, - status: String, - poll_length: u32, - voting_address: String, - chain_id: u32, - ciphernode_count: u32, - pk_share_count: u32, - sks_share_count: u32, - vote_count: u32, - crp: Vec, - pk: Vec, - start_time: i64, - block_start: U64, - ciphernode_total: u32, - emojis: [String; 2], -} - -#[derive(Debug, Deserialize, Serialize)] -struct Round { - id: u32, - status: String, - poll_length: u32, - voting_address: String, - chain_id: u32, - ciphernode_count: u32, - pk_share_count: u32, - sks_share_count: u32, - vote_count: u32, - crp: Vec, - pk: Vec, - start_time: i64, - block_start: U64, - ciphernode_total: u32, - emojis: [String; 2], - votes_option_1: u32, - votes_option_2: u32, - ciphernodes: Vec, - has_voted: Vec, -} - -#[derive(Debug, Deserialize, Serialize)] -struct Ciphernode { - id: u32, - pk_share: Vec, - sks_share: Vec, -} - -#[derive(Debug, Deserialize, Serialize)] -struct GetCiphernode { - round_id: u32, - ciphernode_id: u32, -} - -#[derive(Debug, Deserialize, Serialize)] -struct GetEligibilityRequest { - round_id: u32, - node_id: u32, - is_eligible: bool, - reason: String, -} - -#[derive(Debug, Deserialize, Serialize)] -struct AuthenticationDB { - jwt_tokens: Vec, -} - -#[derive(Debug, Deserialize, Serialize)] -struct AuthenticationLogin { - postId: String, -} - -#[derive(Debug, Deserialize, Serialize)] -struct AuthenticationResponse { - response: String, - jwt_token: String, -} - -fn generate_emoji() -> (String, String) { - let emojis = [ - "🍇","🍈","🍉","🍊","🍋","🍌","🍍","🥭","🍎","🍏", - "🍐","🍑","🍒","🍓","🫐","🥝","🍅","🫒","🥥","🥑", - "🍆","🥔","🥕","🌽","🌶️","🫑","🥒","🥬","🥦","🧄", - "🧅","🍄","🥜","🫘","🌰","🍞","🥐","🥖","🫓","🥨", - "🥯","🥞","🧇","🧀","🍖","🍗","🥩","🥓","🍔","🍟", - "🍕","🌭","🥪","🌮","🌯","🫔","🥙","🧆","🥚","🍳", - "🥘","🍲","🫕","🥣","🥗","🍿","🧈","🧂","🥫","🍱", - "🍘","🍙","🍚","🍛","🍜","🍝","🍠","🍢","🍣","🍤", - "🍥","🥮","🍡","🥟","🥠","🥡","🦀","🦞","🦐","🦑", - "🦪","🍦","🍧","🍨","🍩","🍪","🎂","🍰","🧁","🥧", - "🍫","🍬","🍭","🍮","🍯","🍼","🥛","☕","🍵","🍾", - "🍷","🍸","🍹","🍺","🍻","🥂","🥃", - ]; - let mut index1 = rand::thread_rng().gen_range(0..emojis.len()); - let index2 = rand::thread_rng().gen_range(0..emojis.len()); - if index1 == index2 { - if index1 == emojis.len() { - index1 = index1 - 1; - } else { - index1 = index1 + 1; - }; - }; - (emojis[index1].to_string(), emojis[index2].to_string()) -} - -fn get_state(round_id: u32) -> (Round, String) { - let mut round_key = round_id.to_string(); - round_key.push_str("-storage"); - println!("Database key is {:?}", round_key); - let state_out = GLOBAL_DB.get(round_key.clone()).unwrap().unwrap(); - let state_out_str = str::from_utf8(&state_out).unwrap(); - let state_out_struct: Round = serde_json::from_str(&state_out_str).unwrap(); - (state_out_struct, round_key) -} - -fn get_round_count() -> u32 { - let round_key = "round_count"; - let round_db = GLOBAL_DB.get(round_key).unwrap(); - if round_db == None { - println!("initializing first round in db"); - GLOBAL_DB.insert(round_key, b"0".to_vec()).unwrap(); - } - let round_str = std::str::from_utf8(round_db.unwrap().as_ref()).unwrap().to_string(); - round_str.parse::().unwrap() -} - -#[tokio::main] -async fn broadcast_enc_vote(req: &mut Request) -> IronResult { - let mut payload = String::new(); - // read the POST body - req.body.read_to_string(&mut payload).unwrap(); - let incoming: EncryptedVote = serde_json::from_str(&payload).unwrap(); - let mut response_str = ""; - let mut converter = "".to_string(); - let (mut state, key) = get_state(incoming.round_id); - - for i in 0..state.has_voted.len() { - if state.has_voted[i] == incoming.postId { - response_str = "User Has Already Voted"; - } else { - response_str = "Vote Successful"; - } - }; - - if response_str == "Vote Successful" { - let sol_vote = Bytes::from(incoming.enc_vote_bytes); - let tx_hash = call_contract(sol_vote, state.voting_address.clone()).await.unwrap(); - converter = "0x".to_string(); - for i in 0..32 { - if tx_hash[i] <= 16 { - converter.push_str("0"); - converter.push_str(&format!("{:x}", tx_hash[i])); - } else { - converter.push_str(&format!("{:x}", tx_hash[i])); - } - } - - state.vote_count = state.vote_count + 1; - state.has_voted.push(incoming.postId); - let state_str = serde_json::to_string(&state).unwrap(); - let state_bytes = state_str.into_bytes(); - GLOBAL_DB.insert(key, state_bytes).unwrap(); - }; - - let response = JsonResponseTxHash { response: response_str.to_string(), tx_hash: converter }; - let out = serde_json::to_string(&response).unwrap(); - - let content_type = "application/json".parse::().unwrap(); - println!("Request for round {:?} send vote tx", incoming.round_id); - Ok(Response::with((content_type, status::Ok, out))) -} - -async fn call_contract(enc_vote: Bytes, address: String) -> Result> { - println!("calling voting contract"); - - let infura_val = env!("INFURAKEY"); - let mut rpc_url = "https://sepolia.infura.io/v3/".to_string(); - rpc_url.push_str(&infura_val); - - let provider = Provider::::try_from(rpc_url.clone())?; - // let block_number: U64 = provider.get_block_number().await?; - // println!("{block_number}"); - abigen!( - IVOTE, - r#"[ - function voteEncrypted(bytes memory _encVote) public - function getVote(address id) public returns(bytes memory) - event Transfer(address indexed from, address indexed to, uint256 value) - ]"#, - ); - - //const RPC_URL: &str = "https://eth.llamarpc.com"; - let vote_address: &str = &address; - - let eth_val = env!("PRIVATEKEY"); - let wallet: LocalWallet = eth_val - .parse::().unwrap() - .with_chain_id(11155111 as u64); - - let nonce_manager = provider.clone().nonce_manager(wallet.address()); - let curr_nonce = nonce_manager - .get_transaction_count(wallet.address(), Some(BlockNumber::Pending.into())) - .await? - .as_u64(); - - let client = SignerMiddleware::new(provider.clone(), wallet.clone()); - let address: Address = vote_address.parse()?; - let contract = IVOTE::new(address, Arc::new(client.clone())); - - let test = contract.vote_encrypted(enc_vote).nonce(curr_nonce).send().await?.clone(); - println!("{:?}", test); - Ok(test) -} - -// fn register_cyphernode(req: &mut Request) -> IronResult { - // register ip address or some way to contact nodes when a computation request comes in - -// } - -fn get_round_eligibility(req: &mut Request) -> IronResult { - let mut payload = String::new(); - // read the POST body - req.body.read_to_string(&mut payload).unwrap(); - let mut incoming: GetEligibilityRequest = serde_json::from_str(&payload).unwrap(); - println!("Request node elegibility for round {:?}", incoming.round_id); - - let (state, _key) = get_state(incoming.round_id); - - for i in 1..state.ciphernodes.len() { - println!("checking ciphernode {:?}", i); - println!("server db id {:?}", state.ciphernodes[i as usize].id); - println!("incoming request id {:?}", incoming.node_id); - if state.ciphernodes[i as usize].id == incoming.node_id { - incoming.is_eligible = true; - incoming.reason = "Previously Registered".to_string(); - }; - }; - - if state.ciphernode_total == state.ciphernode_count && incoming.reason != "Previously Registered" { - incoming.is_eligible = false; - incoming.reason = "Round Full".to_string(); - }; - - if state.ciphernode_total > state.ciphernode_count && incoming.reason != "Previously Registered" { - incoming.is_eligible = true; - incoming.reason = "Open Node Spot".to_string(); - }; - - let init_time = Utc::now(); - let timestamp = init_time.timestamp(); - - if timestamp >= (state.start_time + state.poll_length as i64) { - incoming.is_eligible = false; - incoming.reason = "Waiting For New Round".to_string(); - } - - let out = serde_json::to_string(&incoming).unwrap(); - - let content_type = "application/json".parse::().unwrap(); - Ok(Response::with((content_type, status::Ok, out))) -} - -fn get_node_by_round(req: &mut Request) -> IronResult { - let mut payload = String::new(); - // read the POST body - req.body.read_to_string(&mut payload).unwrap(); - let incoming: GetCiphernode = serde_json::from_str(&payload).unwrap(); - println!("Request node data for round {:?}", incoming.round_id); - - let (state, _key) = get_state(incoming.round_id); - let mut cnode = Ciphernode { - id: 0, - pk_share: vec![0], - sks_share: vec![0], - }; - - for i in 0..state.ciphernodes.len() { - if state.ciphernodes[i as usize].id == incoming.ciphernode_id { - cnode.id = state.ciphernodes[i as usize].id; - cnode.pk_share = state.ciphernodes[i as usize].pk_share.clone(); - cnode.sks_share = state.ciphernodes[i as usize].sks_share.clone(); - }; - }; - - if cnode.id != 0 { - let out = serde_json::to_string(&cnode).unwrap(); - - let content_type = "application/json".parse::().unwrap(); - Ok(Response::with((content_type, status::Ok, out))) - } else { - let response = JsonResponse { response: "Ciphernode Not Registered".to_string() }; - let out = serde_json::to_string(&response).unwrap(); - - let content_type = "application/json".parse::().unwrap(); - Ok(Response::with((content_type, status::Ok, out))) - } - - // let response = JsonResponse { response: "Ciphernode Not Registered".to_string() }; - // let out = serde_json::to_string(&response).unwrap(); - - // let content_type = "application/json".parse::().unwrap(); - // Ok(Response::with((content_type, status::Ok, out))) -} - -fn report_tally(req: &mut Request) -> IronResult { - let mut payload = String::new(); - // read the POST body - req.body.read_to_string(&mut payload).unwrap(); - let incoming: ReportTallyRequest = serde_json::from_str(&payload).unwrap(); - println!("Request report tally for round {:?}", incoming.round_id); - - let (mut state, key) = get_state(incoming.round_id); - if state.votes_option_1 == 0 && state.votes_option_2 == 0 { - state.votes_option_1 = incoming.option_1; - state.votes_option_2 = incoming.option_2; - - let state_str = serde_json::to_string(&state).unwrap(); - let state_bytes = state_str.into_bytes(); - GLOBAL_DB.insert(key, state_bytes).unwrap(); - } - let response = JsonResponse { response: "Tally Reported".to_string() }; - let out = serde_json::to_string(&response).unwrap(); - - let content_type = "application/json".parse::().unwrap(); - Ok(Response::with((content_type, status::Ok, out))) -} - -fn get_web_result(req: &mut Request) -> IronResult { - let mut payload = String::new(); - // read the POST body - req.body.read_to_string(&mut payload).unwrap(); - let incoming: GetRoundRequest = serde_json::from_str(&payload).unwrap(); - println!("Request web state for round {:?}", incoming.round_id); - - let (state, _key) = get_state(incoming.round_id); - - let response = WebResultRequest { - round_id: incoming.round_id, - option_1_tally: state.votes_option_1, - option_2_tally: state.votes_option_2, - total_votes: state.votes_option_1 + state.votes_option_2, - option_1_emoji: state.emojis[0].clone(), - option_2_emoji: state.emojis[1].clone(), - end_time: state.start_time + state.poll_length as i64 - }; - - let out = serde_json::to_string(&response).unwrap(); - - let content_type = "application/json".parse::().unwrap(); - Ok(Response::with((content_type, status::Ok, out))) -} - -fn get_web_result_all(req: &mut Request) -> IronResult { - println!("Request all web state."); - - let round_count = get_round_count(); - let mut states: Vec = Vec::with_capacity(round_count as usize); - - for i in 1..round_count { - let (state, _key) = get_state(i); - let web_state = WebResultRequest { - round_id: i, - option_1_tally: state.votes_option_1, - option_2_tally: state.votes_option_2, - total_votes: state.votes_option_1 + state.votes_option_2, - option_1_emoji: state.emojis[0].clone(), - option_2_emoji: state.emojis[1].clone(), - end_time: state.start_time + state.poll_length as i64 - }; - states.push(web_state); - } - - let response = AllWebStates { states: states }; - let out = serde_json::to_string(&response).unwrap(); - - let content_type = "application/json".parse::().unwrap(); - Ok(Response::with((content_type, status::Ok, out))) -} - -fn get_poll_length_by_round(req: &mut Request) -> IronResult { - let mut payload = String::new(); - // read the POST body - req.body.read_to_string(&mut payload).unwrap(); - let mut incoming: PollLengthRequest = serde_json::from_str(&payload).unwrap(); - println!("Request poll length for round {:?}", incoming.round_id); - - let (state, _key) = get_state(incoming.round_id); - incoming.poll_length = state.poll_length; - let out = serde_json::to_string(&incoming).unwrap(); - - let content_type = "application/json".parse::().unwrap(); - Ok(Response::with((content_type, status::Ok, out))) -} - -fn get_emojis_by_round(req: &mut Request) -> IronResult { - let mut payload = String::new(); - // read the POST body - req.body.read_to_string(&mut payload).unwrap(); - let mut incoming: GetEmojisRequest = serde_json::from_str(&payload).unwrap(); - println!("Request emojis for round {:?}", incoming.round_id); - - let (state, _key) = get_state(incoming.round_id); - incoming.emojis = state.emojis; - let out = serde_json::to_string(&incoming).unwrap(); - - let content_type = "application/json".parse::().unwrap(); - Ok(Response::with((content_type, status::Ok, out))) -} - -fn get_round_state(req: &mut Request) -> IronResult { - let mut payload = String::new(); - // read the POST body - req.body.read_to_string(&mut payload).unwrap(); - let incoming: GetRoundRequest = serde_json::from_str(&payload).unwrap(); - println!("Request state for round {:?}", incoming.round_id); - - let (state, _key) = get_state(incoming.round_id); - let out = serde_json::to_string(&state).unwrap(); - - let content_type = "application/json".parse::().unwrap(); - Ok(Response::with((content_type, status::Ok, out))) -} - -fn get_round_state_web(req: &mut Request) -> IronResult { - let mut payload = String::new(); - // read the POST body - req.body.read_to_string(&mut payload).unwrap(); - let incoming: GetRoundRequest = serde_json::from_str(&payload).unwrap(); - println!("Request state for round {:?}", incoming.round_id); - - let (state, _key) = get_state(incoming.round_id); - let state_lite = StateWeb { - id: state.id, - status: state.status, - poll_length: state.poll_length, - voting_address: state.voting_address, - chain_id: state.chain_id, - ciphernode_count: state.ciphernode_count, - pk_share_count: state.pk_share_count, - sks_share_count: state.sks_share_count, - vote_count: state.vote_count, - start_time: state.start_time, - ciphernode_total: state.ciphernode_total, - emojis: state.emojis, - }; - - let out = serde_json::to_string(&state_lite).unwrap(); - - let content_type = "application/json".parse::().unwrap(); - Ok(Response::with((content_type, status::Ok, out))) -} - -fn get_round_state_lite(req: &mut Request) -> IronResult { - let mut payload = String::new(); - // read the POST body - req.body.read_to_string(&mut payload).unwrap(); - let incoming: GetRoundRequest = serde_json::from_str(&payload).unwrap(); - println!("Request state for round {:?}", incoming.round_id); - - let (state, _key) = get_state(incoming.round_id); - let state_lite = StateLite { - id: state.id, - status: state.status, - poll_length: state.poll_length, - voting_address: state.voting_address, - chain_id: state.chain_id, - ciphernode_count: state.ciphernode_count, - pk_share_count: state.pk_share_count, - sks_share_count: state.sks_share_count, - vote_count: state.vote_count, - crp: state.crp, - pk: state.pk, - start_time: state.start_time, - block_start: state.block_start, - ciphernode_total: state.ciphernode_total, - emojis: state.emojis, - }; - - let out = serde_json::to_string(&state_lite).unwrap(); - - let content_type = "application/json".parse::().unwrap(); - Ok(Response::with((content_type, status::Ok, out))) -} - -fn get_vote_count_by_round(req: &mut Request) -> IronResult { - let mut payload = String::new(); - // read the POST body - req.body.read_to_string(&mut payload).unwrap(); - let mut incoming: VoteCountRequest = serde_json::from_str(&payload).unwrap(); - println!("Request vote count for round {:?}", incoming.round_id); - - let (state, _key) = get_state(incoming.round_id); - incoming.vote_count = state.vote_count; - let out = serde_json::to_string(&incoming).unwrap(); - - let content_type = "application/json".parse::().unwrap(); - Ok(Response::with((content_type, status::Ok, out))) -} - -fn get_start_time_by_round(req: &mut Request) -> IronResult { - let mut payload = String::new(); - // read the POST body - req.body.read_to_string(&mut payload).unwrap(); - let mut incoming: TimestampRequest = serde_json::from_str(&payload).unwrap(); - println!("Request start time for round {:?}", incoming.round_id); - - let (state, _key) = get_state(incoming.round_id); - incoming.timestamp = state.start_time; - let out = serde_json::to_string(&incoming).unwrap(); - - let content_type = "application/json".parse::().unwrap(); - Ok(Response::with((content_type, status::Ok, out))) -} - -fn get_crp_by_round(req: &mut Request) -> IronResult { - let mut payload = String::new(); - // read the POST body - req.body.read_to_string(&mut payload).unwrap(); - let mut incoming: CRPRequest = serde_json::from_str(&payload).unwrap(); - println!("Request crp for round {:?}", incoming.round_id); - - let (state, _key) = get_state(incoming.round_id); - incoming.crp_bytes = state.crp; - let out = serde_json::to_string(&incoming).unwrap(); - - let content_type = "application/json".parse::().unwrap(); - Ok(Response::with((content_type, status::Ok, out))) -} - -fn get_pk_by_round(req: &mut Request) -> IronResult { - let mut payload = String::new(); - // read the POST body - req.body.read_to_string(&mut payload).unwrap(); - let mut incoming: PKRequest = serde_json::from_str(&payload).unwrap(); - - let (state, _key) = get_state(incoming.round_id); - incoming.pk_bytes = state.pk; - let out = serde_json::to_string(&incoming).unwrap(); - - let content_type = "application/json".parse::().unwrap(); - println!("Request for round {:?} public key", incoming.round_id); - Ok(Response::with((content_type, status::Ok, out))) -} - -fn get_pk_share_count(req: &mut Request) -> IronResult { - let mut payload = String::new(); - // read the POST body - req.body.read_to_string(&mut payload).unwrap(); - - let mut incoming: PKShareCount = serde_json::from_str(&payload).unwrap(); - - let (state, _key) = get_state(incoming.round_id); - incoming.share_id = state.pk_share_count; - let out = serde_json::to_string(&incoming).unwrap(); - - let content_type = "application/json".parse::().unwrap(); - Ok(Response::with((content_type, status::Ok, out))) -} - -fn get_rounds(_req: &mut Request) -> IronResult { - //let test = _req.headers.get::().unwrap(); - //println!("content_type: {:?}", test); - - // let test3 = _req.headers.get::>().unwrap(); - // println!("auth: {:?}", test3.token); - // let key: Hmac = Hmac::new_from_slice(b"some-secret").unwrap(); - // let claims: BTreeMap = test3.token.verify_with_key(&key).unwrap(); - // println!("decoded hmac {:?}", claims); - - //let test2 = _req.headers.get::(); - //println!("user agent: {:?}", test2); - - let key = "round_count"; - let mut round = GLOBAL_DB.get(key).unwrap(); - if round == None { - println!("initializing first round in db"); - GLOBAL_DB.insert(key, b"0".to_vec()).unwrap(); - round = GLOBAL_DB.get(key).unwrap(); - } - let round_key = std::str::from_utf8(round.unwrap().as_ref()).unwrap().to_string(); - let round_int = round_key.parse::().unwrap(); - - let count = RoundCount {round_count: round_int}; - println!("round_count: {:?}", count.round_count); - - - let out = serde_json::to_string(&count).unwrap(); - println!("get rounds hit"); - - let content_type = "application/json".parse::().unwrap(); - Ok(Response::with((content_type, status::Ok, out))) -} - -#[tokio::main] -async fn init_crisp_round(req: &mut Request) -> IronResult { - // let auth = _req.headers.get::>().unwrap(); - // if auth.token != env { - - // } - println!("generating round crp"); - - let infura_val = env!("INFURAKEY"); - let mut rpc_url = "https://sepolia.infura.io/v3/".to_string(); - rpc_url.push_str(&infura_val); - - let provider = Provider::::try_from(rpc_url.clone()).unwrap(); - let block_number: U64 = provider.get_block_number().await.unwrap(); - - let degree = 4096; - let plaintext_modulus: u64 = 4096; - let moduli = vec![0xffffee001, 0xffffc4001, 0x1ffffe0001]; - - // Let's generate the BFV parameters structure. - let params = timeit!( - "Parameters generation", - BfvParametersBuilder::new() - .set_degree(degree) - .set_plaintext_modulus(plaintext_modulus) - .set_moduli(&moduli) - .build_arc().unwrap() - ); - let crp = CommonRandomPoly::new(¶ms, &mut thread_rng()).unwrap(); - let crp_bytes = crp.to_bytes(); - - let mut payload = String::new(); - - // read the POST body - req.body.read_to_string(&mut payload).unwrap(); - - // we're expecting the POST to match the format of our JsonRequest struct - let incoming: CrispConfig = serde_json::from_str(&payload).unwrap(); - println!("ID: {:?}", incoming.round_id); // TODO: check that client sent the expected next round_id - println!("Address: {:?}", incoming.voting_address); - - // -------------- - let key = "round_count"; - //db.remove(key)?; - let round = GLOBAL_DB.get(key).unwrap(); - if round == None { - println!("initializing first round in db"); - GLOBAL_DB.insert(key, b"0".to_vec()).unwrap(); - } - let round_key = std::str::from_utf8(round.unwrap().as_ref()).unwrap().to_string(); - let mut round_int = round_key.parse::().unwrap(); - round_int = round_int + 1; - let mut inc_round_key = round_int.to_string(); - inc_round_key.push_str("-storage"); - println!("Database key is {:?} and round int is {:?}", inc_round_key, round_int); - - let init_time = Utc::now(); - let timestamp = init_time.timestamp(); - println!("timestamp {:?}", timestamp); - - let (emoji1, emoji2) = generate_emoji(); - - let state = Round { - id: round_int, - status: "Active".to_string(), - poll_length: incoming.poll_length, - voting_address: incoming.voting_address, - chain_id: incoming.chain_id, - ciphernode_count: 0, - pk_share_count: 0, - sks_share_count: 0, - vote_count: 0, - crp: crp_bytes, - pk: vec![0], - start_time: timestamp, - block_start: block_number, - ciphernode_total: incoming.ciphernode_count, - emojis: [emoji1, emoji2], - votes_option_1: 0, - votes_option_2: 0, - ciphernodes: vec![ - Ciphernode { - id: 0, - pk_share: vec![0], - sks_share: vec![0], - } - ], - has_voted: vec!["".to_string()], - }; - - let state_str = serde_json::to_string(&state).unwrap(); - let state_bytes = state_str.into_bytes(); - let key2 = round_int.to_string(); - GLOBAL_DB.insert(inc_round_key, state_bytes).unwrap(); - - let new_round_bytes = key2.into_bytes(); - GLOBAL_DB.insert(key, new_round_bytes).unwrap(); - - // create a response with our random string, and pass in the string from the POST body - let response = JsonResponse { response: "CRISP Initiated".to_string() }; - let out = serde_json::to_string(&response).unwrap(); - - let content_type = "application/json".parse::().unwrap(); - Ok(Response::with((content_type, status::Ok, out))) -} - - -async fn aggregate_pk_shares(round_id: u32) -> Result<(), Box> { - println!("aggregating validator keyshare"); - - let degree = 4096; - let plaintext_modulus: u64 = 4096; - let moduli = vec![0xffffee001, 0xffffc4001, 0x1ffffe0001]; - - // Generate a deterministic seed for the Common Poly - //let mut seed = ::Seed::default(); - - // Let's generate the BFV parameters structure. - let params = timeit!( - "Parameters generation", - BfvParametersBuilder::new() - .set_degree(degree) - .set_plaintext_modulus(plaintext_modulus) - .set_moduli(&moduli) - .build_arc()? - ); - - let mut round_key = round_id.to_string(); - round_key.push_str("-storage"); - println!("Database key is {:?}", round_key); - - let state_out = GLOBAL_DB.get(round_key.clone()).unwrap().unwrap(); - let state_out_str = str::from_utf8(&state_out).unwrap(); - let mut state: Round = serde_json::from_str(&state_out_str).unwrap(); - println!("checking db after drop {:?}", state.ciphernode_count); - println!("{:?}", state.ciphernodes[0].id); - //println!("{:?}", state.ciphernodes[0].pk_share); - - //let crp = CommonRandomPoly::new_deterministic(¶ms, seed)?; - let crp = CommonRandomPoly::deserialize(&state.crp, ¶ms)?; - - // Party setup: each party generates a secret key and shares of a collective - // public key. - struct Party { - pk_share: PublicKeyShare, - } - - let mut parties :Vec = Vec::new(); - for i in 1..state.ciphernode_total + 1 { // todo fix init code that causes offset - // read in pk_shares from storage - println!("Aggregating PKShare... id {}", i); - let data_des = PublicKeyShare::deserialize(&state.ciphernodes[i as usize].pk_share, ¶ms, crp.clone()).unwrap(); - // let pk_share = PublicKeyShare::new(&sk_share, crp.clone(), &mut thread_rng())?; - parties.push(Party { pk_share: data_des }); - } - - // Aggregation: this could be one of the parties or a separate entity. Or the - // parties can aggregate cooperatively, in a tree-like fashion. - let pk = timeit!("Public key aggregation", { - let pk: PublicKey = parties.iter().map(|p| p.pk_share.clone()).aggregate()?; - pk - }); - //println!("{:?}", pk); - println!("Multiparty Public Key Generated"); - let store_pk = pk.to_bytes(); - state.pk = store_pk; - let state_str = serde_json::to_string(&state).unwrap(); - let state_bytes = state_str.into_bytes(); - GLOBAL_DB.insert(round_key, state_bytes).unwrap(); - println!("aggregate pk stored for round {:?}", round_id); - Ok(()) -} - -fn handler(_req: &mut Request) -> IronResult { - let response = JsonResponse { response: pick_response() }; - let out = serde_json::to_string(&response).unwrap(); - println!("index handler hit"); - let content_type = "application/json".parse::().unwrap(); - Ok(Response::with((content_type, status::Ok, out))) -} - -fn health_handler(_req: &mut Request) -> IronResult { - let content_type = "application/json".parse::().unwrap(); - Ok(Response::with((content_type, status::Ok))) -} - -// polling endpoint for sks shares - -fn register_sks_share(req: &mut Request) -> IronResult { - let mut payload = String::new(); - - // read the POST body - req.body.read_to_string(&mut payload).unwrap(); - - // we're expecting the POST to match the format of our JsonRequest struct - let incoming: SKSShareRequest = serde_json::from_str(&payload).unwrap(); - println!("{:?}", incoming.response); - println!("Index: {:?}", incoming.index); // cipher node id (based on first upload of pk share) - println!("Round ID: {:?}", incoming.round_id); - - - let mut round_key = incoming.round_id.to_string(); - round_key.push_str("-storage"); - println!("Database key is {:?}", round_key); - - let state_out = GLOBAL_DB.get(round_key.clone()).unwrap().unwrap(); - let state_out_str = str::from_utf8(&state_out).unwrap(); - let mut state_out_struct: Round = serde_json::from_str(&state_out_str).unwrap(); - state_out_struct.sks_share_count = state_out_struct.sks_share_count + 1; - - let index = incoming.index; // TODO use hashmap with node id as key - state_out_struct.ciphernodes[index as usize].sks_share = incoming.sks_share; - let state_str = serde_json::to_string(&state_out_struct).unwrap(); - let state_bytes = state_str.into_bytes(); - GLOBAL_DB.insert(round_key, state_bytes).unwrap(); - println!("sks share stored for node index {:?}", incoming.index); - - // toso get share threshold from client config - if state_out_struct.sks_share_count == state_out_struct.ciphernode_total { - println!("All sks shares received"); - //aggregate_pk_shares(incoming.round_id).await; - // TODO: maybe notify cipher nodes - } - - // create a response with our random string, and pass in the string from the POST body - let response = JsonResponse { response: pick_response() }; - let out = serde_json::to_string(&response).unwrap(); - - let content_type = "application/json".parse::().unwrap(); - Ok(Response::with((content_type, status::Ok, out))) -} - -fn get_sks_shares(req: &mut Request) -> IronResult { - let mut payload = String::new(); - - // read the POST body - req.body.read_to_string(&mut payload).unwrap(); - - // we're expecting the POST to match the format of our JsonRequest struct - let incoming: SKSSharePoll = serde_json::from_str(&payload).unwrap(); - //const length: usize = incoming.cyphernode_count; - - let (mut state, key) = get_state(incoming.round_id); - - let mut shares = Vec::with_capacity(incoming.ciphernode_count as usize); - - // toso get share threshold from client config - if state.sks_share_count == state.ciphernode_total { - println!("All sks shares received... sending to cipher nodes"); - for i in 1..state.ciphernode_total + 1 { - println!("reading share {:?}", i); - shares.push(state.ciphernodes[i as usize].sks_share.clone()); - } - let response = SKSShareResponse { - response: "final".to_string(), - round_id: incoming.round_id, - sks_shares: shares, - }; - state.status = "Finalized".to_string(); - let state_str = serde_json::to_string(&state).unwrap(); - let state_bytes = state_str.into_bytes(); - GLOBAL_DB.insert(key, state_bytes).unwrap(); - let out = serde_json::to_string(&response).unwrap(); - println!("get rounds hit"); - - let content_type = "application/json".parse::().unwrap(); - Ok(Response::with((content_type, status::Ok, out))) - } else { - let response = SKSShareResponse { - response: "waiting".to_string(), - round_id: incoming.round_id, - sks_shares: shares, - }; - let out = serde_json::to_string(&response).unwrap(); - println!("get rounds hit"); - - let content_type = "application/json".parse::().unwrap(); - Ok(Response::with((content_type, status::Ok, out))) - } -} - -#[tokio::main] -async fn register_ciphernode(req: &mut Request) -> IronResult { - let mut payload = String::new(); - - // read the POST body - req.body.read_to_string(&mut payload).unwrap(); - - // we're expecting the POST to match the format of our JsonRequest struct - let incoming: JsonRequest = serde_json::from_str(&payload).unwrap(); - println!("{:?}", incoming.response); - println!("ID: {:?}", incoming.id); - println!("Round ID: {:?}", incoming.round_id); - - let (mut state, key) = get_state(incoming.round_id); - - state.pk_share_count = state.pk_share_count + 1; - state.ciphernode_count = state.ciphernode_count + 1; - let cnode = Ciphernode { - id: incoming.id, - pk_share: incoming.pk_share, - sks_share: vec![0], - }; - state.ciphernodes.push(cnode); - let state_str = serde_json::to_string(&state).unwrap(); - let state_bytes = state_str.into_bytes(); - GLOBAL_DB.insert(key, state_bytes).unwrap(); - - println!("pk share store for node id {:?}", incoming.id); - println!("ciphernode count {:?}", state.ciphernode_count); - println!("ciphernode total {:?}", state.ciphernode_total); - println!("pk share count {:?}", state.pk_share_count); - - if state.ciphernode_count == state.ciphernode_total { - println!("All shares received"); - let _ = aggregate_pk_shares(incoming.round_id).await; - } - - let response = RegisterNodeResponse { - response: "Node Registered".to_string(), - node_index: state.ciphernode_count, - }; - let out = serde_json::to_string(&response).unwrap(); - - let content_type = "application/json".parse::().unwrap(); - Ok(Response::with((content_type, status::Ok, out))) -} - -fn authentication_login(req: &mut Request) -> IronResult { - let mut payload = String::new(); - // read the POST body - req.body.read_to_string(&mut payload).unwrap(); - let incoming: AuthenticationLogin = serde_json::from_str(&payload).unwrap(); - println!("Twitter Login Request"); - - // hmac - let hmac_key: Hmac = Hmac::new_from_slice(b"some-secret").unwrap(); - let mut claims = BTreeMap::new(); - claims.insert("postId", incoming.postId); - let token_str = claims.sign_with_key(&hmac_key).unwrap(); - - // db - let key = "authentication"; - let mut authsdb = GLOBAL_DB.get(key).unwrap(); - let mut response_str = "".to_string(); - let mut jwt_token = "".to_string(); - - if authsdb == None { - println!("initializing first auth in db"); - // hmac - let auth_struct = AuthenticationDB { - jwt_tokens: vec![token_str.clone()], - }; - let authsdb_str = serde_json::to_string(&auth_struct).unwrap(); - let authsdb_bytes = authsdb_str.into_bytes(); - GLOBAL_DB.insert(key, authsdb_bytes).unwrap(); - // set response - response_str = "Authorized".to_string(); - } else { - // look for previous auth - let mut au_db = authsdb.unwrap(); - let authsdb_out_str = str::from_utf8(&au_db).unwrap(); - let mut authsdb_out_struct: AuthenticationDB = serde_json::from_str(&authsdb_out_str).unwrap(); - - for i in 0..authsdb_out_struct.jwt_tokens.len() { - if authsdb_out_struct.jwt_tokens[i as usize] == token_str { - println!("Found previous login."); - response_str = "Already Authorized".to_string(); - } - }; - - if response_str != "Already Authorized" { - println!("Inserting new login to db."); - authsdb_out_struct.jwt_tokens.push(token_str.clone()); - let authsdb_str = serde_json::to_string(&authsdb_out_struct).unwrap(); - let authsdb_bytes = authsdb_str.into_bytes(); - GLOBAL_DB.insert(key, authsdb_bytes).unwrap(); - response_str = "Authorized".to_string(); - } - }; - - let response = AuthenticationResponse { - response: response_str, - jwt_token: token_str, - }; - let out = serde_json::to_string(&response).unwrap(); - - let content_type = "application/json".parse::().unwrap(); - Ok(Response::with((content_type, status::Ok, out))) -} - -#[tokio::main] -async fn main() -> Result<(), Box> { - // Server Code - let mut router = Router::new(); - router.get("/", handler, "index"); - router.get("/health", health_handler, "health"); - router.get("/get_rounds", get_rounds, "get_rounds"); - router.get("/get_web_result_all", get_web_result_all, "get_web_result_all"); - router.post("/get_pk_share_count", get_pk_share_count, "get_pk_share_count"); - router.post("/register_ciphernode", register_ciphernode, "register_ciphernode"); - router.post("/init_crisp_round", init_crisp_round, "init_crisp_round"); - router.post("/get_pk_by_round", get_pk_by_round, "get_pk_by_round"); - router.post("/register_sks_share", register_sks_share, "register_sks_share"); - router.post("/get_sks_shares", get_sks_shares, "get_sks_shares"); - router.post("/get_crp_by_round", get_crp_by_round, "get_crp_by_round"); - router.post("/broadcast_enc_vote", broadcast_enc_vote, "broadcast_enc_vote"); - router.post("/get_vote_count_by_round", get_vote_count_by_round, "get_vote_count_by_round"); - router.post("/get_start_time_by_round", get_start_time_by_round, "get_start_time_by_round"); - router.post("/get_emojis_by_round", get_emojis_by_round, "get_emojis_by_round"); - router.post("/get_poll_length_by_round", get_poll_length_by_round, "get_poll_length_by_round"); - router.post("/get_round_state_lite", get_round_state_lite, "get_round_state_lite"); - router.post("/get_round_state", get_round_state, "get_round_state"); - router.post("/report_tally", report_tally, "report_tally"); - router.post("/get_web_result", get_web_result, "get_web_result"); - router.post("/get_round_state_web", get_round_state_web, "get_round_state_web"); - router.post("/get_node_by_round", get_node_by_round, "get_node_by_round"); - router.post("/get_round_eligibility", get_round_eligibility, "get_round_eligibility"); - router.post("/authentication_login", authentication_login, "authentication_login"); - - let cors_middleware = CorsMiddleware::with_allow_any(); - println!("Allowed origin hosts: *"); - - let mut chain = Chain::new(router); - chain.link_around(cors_middleware); - Iron::new(chain).http("0.0.0.0:4000").unwrap(); - - Ok(()) +fn main() -> Result<(), Box> { + start_server() } diff --git a/packages/server/src/bin/start_rounds.rs b/packages/server/src/bin/start_rounds.rs index b75b260..bd5e1ea 100644 --- a/packages/server/src/bin/start_rounds.rs +++ b/packages/server/src/bin/start_rounds.rs @@ -1,5 +1,4 @@ -mod util; - +use rfv::util::timeit::timeit; use dialoguer::{theme::ColorfulTheme, Input, FuzzySelect}; use std::{thread, time, env}; use serde::{Deserialize, Serialize}; @@ -11,7 +10,6 @@ use fhe::{ }; use fhe_traits::{FheEncoder, FheEncrypter, Serialize as FheSerialize, DeserializeParametrized}; use rand::{thread_rng}; -use util::timeit::{timeit}; use hyper::Request; use hyper::Method; diff --git a/packages/server/src/cli/auth.rs b/packages/server/src/cli/auth.rs new file mode 100644 index 0000000..c17ed8f --- /dev/null +++ b/packages/server/src/cli/auth.rs @@ -0,0 +1,38 @@ +use hyper::{Request, Method}; +use http_body_util::BodyExt; +use serde::{Deserialize, Serialize}; +use crate::cli::HyperClientPost; + +#[derive(Debug, Deserialize, Serialize)] +pub struct AuthenticationLogin { + #[allow(non_snake_case)] + pub postId: String, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct AuthenticationResponse { + pub response: String, + pub jwt_token: String, +} + +pub async fn authenticate_user(config: &super::CrispConfig, client: &HyperClientPost) -> Result> { + let user = AuthenticationLogin { + postId: config.authentication_id.clone(), + }; + + let out = serde_json::to_string(&user).unwrap(); + let mut url = config.enclave_address.clone(); + url.push_str("/authentication_login"); + let req = Request::builder() + .method(Method::POST) + .uri(url) + .body(out)?; + + let resp = client.request(req).await?; + let body_bytes = resp.collect().await?.to_bytes(); + let body_str = String::from_utf8(body_bytes.to_vec()).unwrap(); + let auth_res: AuthenticationResponse = serde_json::from_str(&body_str).expect("JSON was not well-formatted"); + + println!("Authentication response {:?}", auth_res); + Ok(auth_res) +} diff --git a/packages/server/src/cli/mod.rs b/packages/server/src/cli/mod.rs new file mode 100644 index 0000000..11f249f --- /dev/null +++ b/packages/server/src/cli/mod.rs @@ -0,0 +1,90 @@ +mod auth; +mod voting; + +use dialoguer::{theme::ColorfulTheme, FuzzySelect}; +use hyper_tls::HttpsConnector; +use hyper_util::{client::legacy::{Client as HyperClient, connect::HttpConnector}, rt::TokioExecutor}; +use bytes::Bytes; +use std::env; +use std::fs::File; +use std::io::Read; +use http_body_util::Empty; + +use auth::{authenticate_user, AuthenticationResponse}; +use voting::{initialize_crisp_round, participate_in_existing_round}; +use serde::{Deserialize, Serialize}; + +type HyperClientGet = HyperClient, Empty>; +type HyperClientPost = HyperClient, String>; + +#[derive(Debug, Deserialize, Serialize)] +struct CrispConfig { + round_id: u32, + poll_length: u32, + chain_id: u32, + voting_address: String, + ciphernode_count: u32, + enclave_address: String, + authentication_id: String, +} + +#[tokio::main] +pub async fn run_cli() -> Result<(), Box> { + let https = HttpsConnector::new(); + let client_get: HyperClientGet = HyperClient::builder(TokioExecutor::new()).build(https.clone()); + let client: HyperClientPost = HyperClient::builder(TokioExecutor::new()).build(https); + + let mut auth_res = AuthenticationResponse { + response: "".to_string(), + jwt_token: "".to_string(), + }; + + print!("{esc}[2J{esc}[1;1H", esc = 27 as char); + let selections = &[ + "CRISP: Voting Protocol (ETH)", + "More Coming Soon!" + ]; + + let selection_1 = FuzzySelect::with_theme(&ColorfulTheme::default()) + .with_prompt("Enclave (EEEE): Please choose the private execution environment you would like to run!") + .default(0) + .items(&selections[..]) + .interact() + .unwrap(); + + if selection_1 == 0 { + print!("{esc}[2J{esc}[1;1H", esc = 27 as char); + let selections_2 = &[ + "Initialize new CRISP round.", + "Continue Existing CRISP round." + ]; + + let selection_2 = FuzzySelect::with_theme(&ColorfulTheme::default()) + .with_prompt("Create a new CRISP round or participate in an existing round.") + .default(0) + .items(&selections_2[..]) + .interact() + .unwrap(); + + // Read configuration + let path = env::current_dir().unwrap(); + let mut pathst = path.display().to_string(); + pathst.push_str("/example_config.json"); + let mut file = File::open(pathst).unwrap(); + let mut data = String::new(); + file.read_to_string(&mut data).unwrap(); + let config: CrispConfig = serde_json::from_str(&data).expect("JSON was not well-formatted"); + + if selection_2 == 0 { + initialize_crisp_round(&config, &client_get, &client).await?; + } else if selection_2 == 1 { + auth_res = authenticate_user(&config, &client).await?; + participate_in_existing_round(&config, &client, &auth_res).await?; + } + } else { + println!("Check back soon!"); + std::process::exit(1); + } + + Ok(()) +} diff --git a/packages/server/src/cli/voting.rs b/packages/server/src/cli/voting.rs new file mode 100644 index 0000000..2597200 --- /dev/null +++ b/packages/server/src/cli/voting.rs @@ -0,0 +1,211 @@ +use bytes::Bytes; +use dialoguer::{theme::ColorfulTheme, FuzzySelect, Input}; +use http_body_util::BodyExt; +use http_body_util::Empty; +use hyper::{Method, Request}; +use serde::{Deserialize, Serialize}; +use std::{thread, time}; +use tokio::io::{self, AsyncWriteExt as _}; + +use crate::cli::AuthenticationResponse; +use crate::cli::{HyperClientGet, HyperClientPost}; +use crate::util::timeit::timeit; +use fhe::bfv::{BfvParametersBuilder, Encoding, Plaintext, PublicKey}; +use fhe_traits::{DeserializeParametrized, FheEncoder, FheEncrypter, Serialize as FheSerialize}; +use rand::thread_rng; + +#[derive(Debug, Deserialize, Serialize)] +struct JsonRequestGetRounds { + response: String, +} + +#[derive(Debug, Deserialize, Serialize)] +struct RoundCount { + round_count: u32, +} + +#[derive(Debug, Deserialize, Serialize)] +struct PKRequest { + round_id: u32, + pk_bytes: Vec, +} + +#[derive(Debug, Deserialize, Serialize)] +struct EncryptedVote { + round_id: u32, + enc_vote_bytes: Vec, + #[allow(non_snake_case)] + postId: String, +} + +#[derive(Debug, Deserialize, Serialize)] +struct JsonResponseTxHash { + response: String, + tx_hash: String, +} + +pub async fn initialize_crisp_round( + config: &super::CrispConfig, + client_get: &HyperClientGet, + client: &HyperClientPost, +) -> Result<(), Box> { + println!("Starting new CRISP round!"); + + println!("Initializing Keyshare nodes..."); + + let response_id = JsonRequestGetRounds { + response: "Test".to_string(), + }; + let _out = serde_json::to_string(&response_id).unwrap(); + let mut url_id = config.enclave_address.clone(); + url_id.push_str("/get_rounds"); + + let req = Request::builder() + .method(Method::GET) + .uri(url_id) + .body(Empty::::new())?; + + let resp = client_get.request(req).await?; + + println!("Response status: {}", resp.status()); + + let body_bytes = resp.collect().await?.to_bytes(); + let body_str = String::from_utf8(body_bytes.to_vec()).unwrap(); + let count: RoundCount = serde_json::from_str(&body_str).expect("JSON was not well-formatted"); + println!("Server Round Count: {:?}", count.round_count); + + let round_id = count.round_count + 1; + let response = super::CrispConfig { + round_id: round_id, + poll_length: config.poll_length, + chain_id: config.chain_id, + voting_address: config.voting_address.clone(), + ciphernode_count: config.ciphernode_count, + enclave_address: config.enclave_address.clone(), + authentication_id: config.authentication_id.clone(), + }; + let out = serde_json::to_string(&response).unwrap(); + let mut url = config.enclave_address.clone(); + url.push_str("/init_crisp_round"); + let req = Request::builder() + .header("authorization", "Bearer fpKL54jvWmEGVoRdCNjG") + .method(Method::POST) + .uri(url) + .body(out)?; + + let mut resp = client.request(req).await?; + + println!("Response status: {}", resp.status()); + + while let Some(next) = resp.frame().await { + let frame = next?; + if let Some(chunk) = frame.data_ref() { + tokio::io::stdout().write_all(chunk).await?; + } + } + println!("Round Initialized."); + println!("Gathering Keyshare nodes for execution environment..."); + let three_seconds = time::Duration::from_millis(1000); + thread::sleep(three_seconds); + println!("\nYou can now vote Encrypted with Round ID: {:?}", round_id); + + Ok(()) +} + +pub async fn participate_in_existing_round( + config: &super::CrispConfig, + client: &HyperClientPost, + auth_res: &AuthenticationResponse, +) -> Result<(), Box> { + let input_crisp_id: u32 = Input::with_theme(&ColorfulTheme::default()) + .with_prompt("Enter CRISP round ID.") + .interact_text() + .unwrap(); + println!("Voting state Initialized"); + + // get public encrypt key + let v: Vec = vec![0]; + let response_pk = PKRequest { + round_id: input_crisp_id, + pk_bytes: v, + }; + let out = serde_json::to_string(&response_pk).unwrap(); + let mut url = config.enclave_address.clone(); + url.push_str("/get_pk_by_round"); + let req = Request::builder().method(Method::POST).uri(url).body(out)?; + + let resp = client.request(req).await?; + + println!("Response status: {}", resp.status()); + + let body_bytes = resp.collect().await?.to_bytes(); + let body_str = String::from_utf8(body_bytes.to_vec()).unwrap(); + let pk_res: PKRequest = serde_json::from_str(&body_str).expect("JSON was not well-formatted"); + println!( + "Shared Public Key for CRISP round {:?} collected.", + pk_res.round_id + ); + + let degree = 4096; + let plaintext_modulus: u64 = 4096; + let moduli = vec![0xffffee001, 0xffffc4001, 0x1ffffe0001]; + // Let's generate the BFV parameters structure. + let params = timeit!( + "Parameters generation", + BfvParametersBuilder::new() + .set_degree(degree) + .set_plaintext_modulus(plaintext_modulus) + .set_moduli(&moduli) + .build_arc()? + ); + let pk_deserialized = PublicKey::from_bytes(&pk_res.pk_bytes, ¶ms).unwrap(); + + // Select voting option + let selections_3 = &["Abstain.", "Vote yes.", "Vote no."]; + + let selection_3 = FuzzySelect::with_theme(&ColorfulTheme::default()) + .with_prompt("Please select your voting option.") + .default(0) + .items(&selections_3[..]) + .interact() + .unwrap(); + + let mut vote_choice: u64 = 0; + if selection_3 == 0 { + println!("Exiting voting system. You may choose to vote later."); + return Ok(()); + } else if selection_3 == 1 { + vote_choice = 1; + } else if selection_3 == 2 { + vote_choice = 0; + } + println!("Encrypting vote."); + let votes: Vec = [vote_choice].to_vec(); + let pt = Plaintext::try_encode(&[votes[0]], Encoding::poly(), ¶ms)?; + let ct = pk_deserialized.try_encrypt(&pt, &mut thread_rng())?; + println!("Vote encrypted."); + println!("Calling voting contract with encrypted vote."); + + let request_contract = EncryptedVote { + round_id: input_crisp_id, + enc_vote_bytes: ct.to_bytes(), + postId: auth_res.jwt_token.clone(), + }; + let out = serde_json::to_string(&request_contract).unwrap(); + let mut url = config.enclave_address.clone(); + url.push_str("/broadcast_enc_vote"); + let req = Request::builder().method(Method::POST).uri(url).body(out)?; + + let resp = client.request(req).await?; + + println!("Response status: {}", resp.status()); + + let body_bytes = resp.collect().await?.to_bytes(); + let body_str = String::from_utf8(body_bytes.to_vec()).unwrap(); + let contract_res: JsonResponseTxHash = + serde_json::from_str(&body_str).expect("JSON was not well-formatted"); + println!("Contract call: {:?}", contract_res.response); + println!("TxHash is {:?}", contract_res.tx_hash); + + Ok(()) +} diff --git a/packages/server/src/enclave_server/database.rs b/packages/server/src/enclave_server/database.rs new file mode 100644 index 0000000..aa7f3ad --- /dev/null +++ b/packages/server/src/enclave_server/database.rs @@ -0,0 +1,66 @@ + +use std::{env, str}; +use once_cell::sync::Lazy; +use sled::Db; +use rand::Rng; +use super::models::Round; + +pub static GLOBAL_DB: Lazy = Lazy::new(|| { + let pathdb = env::current_dir().unwrap(); + let mut pathdbst = pathdb.display().to_string(); + pathdbst.push_str("/database/enclave_server"); + sled::open(pathdbst.clone()).unwrap() +}); + +pub fn get_state(round_id: u32) -> (Round, String) { + let mut round_key = round_id.to_string(); + round_key.push_str("-storage"); + println!("Database key is {:?}", round_key); + let state_out = GLOBAL_DB.get(round_key.clone()).unwrap().unwrap(); + let state_out_str = str::from_utf8(&state_out).unwrap(); + let state_out_struct: Round = serde_json::from_str(&state_out_str).unwrap(); + (state_out_struct, round_key) +} + +pub fn get_round_count() -> u32 { + let round_key = "round_count"; + let round_db = GLOBAL_DB.get(round_key).unwrap(); + if round_db == None { + println!("initializing first round in db"); + GLOBAL_DB.insert(round_key, b"0".to_vec()).unwrap(); + } + let round_str = std::str::from_utf8(round_db.unwrap().as_ref()).unwrap().to_string(); + round_str.parse::().unwrap() +} + + +pub fn generate_emoji() -> (String, String) { + let emojis = [ + "🍇","🍈","🍉","🍊","🍋","🍌","🍍","🥭","🍎","🍏", + "🍐","🍑","🍒","🍓","🫐","🥝","🍅","🫒","🥥","🥑", + "🍆","🥔","🥕","🌽","🌶️","🫑","🥒","🥬","🥦","🧄", + "🧅","🍄","🥜","🫘","🌰","🍞","🥐","🥖","🫓","🥨", + "🥯","🥞","🧇","🧀","🍖","🍗","🥩","🥓","🍔","🍟", + "🍕","🌭","🥪","🌮","🌯","🫔","🥙","🧆","🥚","🍳", + "🥘","🍲","🫕","🥣","🥗","🍿","🧈","🧂","🥫","🍱", + "🍘","🍙","🍚","🍛","🍜","🍝","🍠","🍢","🍣","🍤", + "🍥","🥮","🍡","🥟","🥠","🥡","🦀","🦞","🦐","🦑", + "🦪","🍦","🍧","🍨","🍩","🍪","🎂","🍰","🧁","🥧", + "🍫","🍬","🍭","🍮","🍯","🍼","🥛","☕","🍵","🍾", + "🍷","🍸","🍹","🍺","🍻","🥂","🥃", + ]; + let mut index1 = rand::thread_rng().gen_range(0..emojis.len()); + let index2 = rand::thread_rng().gen_range(0..emojis.len()); + if index1 == index2 { + if index1 == emojis.len() { + index1 = index1 - 1; + } else { + index1 = index1 + 1; + }; + }; + (emojis[index1].to_string(), emojis[index2].to_string()) +} + +pub fn pick_response() -> String { + "Test".to_string() +} \ No newline at end of file diff --git a/packages/server/src/enclave_server/mod.rs b/packages/server/src/enclave_server/mod.rs new file mode 100644 index 0000000..6e6c39c --- /dev/null +++ b/packages/server/src/enclave_server/mod.rs @@ -0,0 +1,23 @@ +mod routes; +mod models; +mod database; + +use iron::prelude::*; +use iron::Chain; +use router::Router; +use iron_cors::CorsMiddleware; + +#[tokio::main] +pub async fn start_server() -> Result<(), Box> { + let mut router = Router::new(); + routes::setup_routes(&mut router); + + let cors_middleware = CorsMiddleware::with_allow_any(); + println!("Allowed origin hosts: *"); + + let mut chain = Chain::new(router); + chain.link_around(cors_middleware); + Iron::new(chain).http("0.0.0.0:4000").unwrap(); + + Ok(()) +} \ No newline at end of file diff --git a/packages/server/src/enclave_server/models.rs b/packages/server/src/enclave_server/models.rs new file mode 100644 index 0000000..8dcec95 --- /dev/null +++ b/packages/server/src/enclave_server/models.rs @@ -0,0 +1,239 @@ +use ethers::types::U64; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Deserialize, Serialize)] +pub struct JsonResponse { + pub response: String, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct RegisterNodeResponse { + pub response: String, + pub node_index: u32, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct JsonResponseTxHash { + pub response: String, + pub tx_hash: String, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct JsonRequest { + pub response: String, + pub pk_share: Vec, + pub id: u32, + pub round_id: u32, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct CrispConfig { + pub round_id: u32, + pub poll_length: u32, + pub chain_id: u32, + pub voting_address: String, + pub ciphernode_count: u32, + pub enclave_address: String, + pub authentication_id: String, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct RoundCount { + pub round_count: u32, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct PKShareCount { + pub round_id: u32, + pub share_id: u32, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct PKRequest { + pub round_id: u32, + pub pk_bytes: Vec, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct CRPRequest { + pub round_id: u32, + pub crp_bytes: Vec, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct TimestampRequest { + pub round_id: u32, + pub timestamp: i64, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct PollLengthRequest { + pub round_id: u32, + pub poll_length: u32, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct VoteCountRequest { + pub round_id: u32, + pub vote_count: u32, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct SKSShareRequest { + pub response: String, + pub sks_share: Vec, + pub index: u32, + pub round_id: u32, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct EncryptedVote { + pub round_id: u32, + pub enc_vote_bytes: Vec, + #[allow(non_snake_case)] + pub postId: String, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct GetRoundRequest { + pub round_id: u32, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct GetEmojisRequest { + pub round_id: u32, + pub emojis: [String; 2], +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct SKSSharePoll { + pub response: String, + pub round_id: u32, + pub ciphernode_count: u32, //TODO: dont need this +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct SKSShareResponse { + pub response: String, + pub round_id: u32, + pub sks_shares: Vec>, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct ReportTallyRequest { + pub round_id: u32, + pub option_1: u32, + pub option_2: u32, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct WebResultRequest { + pub round_id: u32, + pub option_1_tally: u32, + pub option_2_tally: u32, + pub total_votes: u32, + pub option_1_emoji: String, + pub option_2_emoji: String, + pub end_time: i64, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct AllWebStates { + pub states: Vec, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct StateWeb { + pub id: u32, + pub status: String, + pub poll_length: u32, + pub voting_address: String, + pub chain_id: u32, + pub ciphernode_count: u32, + pub pk_share_count: u32, + pub sks_share_count: u32, + pub vote_count: u32, + pub start_time: i64, + pub ciphernode_total: u32, + pub emojis: [String; 2], +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct StateLite { + pub id: u32, + pub status: String, + pub poll_length: u32, + pub voting_address: String, + pub chain_id: u32, + pub ciphernode_count: u32, + pub pk_share_count: u32, + pub sks_share_count: u32, + pub vote_count: u32, + pub crp: Vec, + pub pk: Vec, + pub start_time: i64, + pub block_start: U64, + pub ciphernode_total: u32, + pub emojis: [String; 2], +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct Round { + pub id: u32, + pub status: String, + pub poll_length: u32, + pub voting_address: String, + pub chain_id: u32, + pub ciphernode_count: u32, + pub pk_share_count: u32, + pub sks_share_count: u32, + pub vote_count: u32, + pub crp: Vec, + pub pk: Vec, + pub start_time: i64, + pub block_start: U64, + pub ciphernode_total: u32, + pub emojis: [String; 2], + pub votes_option_1: u32, + pub votes_option_2: u32, + pub ciphernodes: Vec, + pub has_voted: Vec, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct Ciphernode { + pub id: u32, + pub pk_share: Vec, + pub sks_share: Vec, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct GetCiphernode { + pub round_id: u32, + pub ciphernode_id: u32, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct GetEligibilityRequest { + pub round_id: u32, + pub node_id: u32, + pub is_eligible: bool, + pub reason: String, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct AuthenticationDB { + pub jwt_tokens: Vec, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct AuthenticationLogin { + #[allow(non_snake_case)] + pub postId: String, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct AuthenticationResponse { + pub response: String, + pub jwt_token: String, +} diff --git a/packages/server/src/enclave_server/routes/ciphernode.rs b/packages/server/src/enclave_server/routes/ciphernode.rs new file mode 100644 index 0000000..e1b00f2 --- /dev/null +++ b/packages/server/src/enclave_server/routes/ciphernode.rs @@ -0,0 +1,365 @@ +use std::str; +use chrono::Utc; +use fhe::{ + bfv::{BfvParametersBuilder, PublicKey}, + mbfv::{AggregateIter, CommonRandomPoly, PublicKeyShare}, +}; +use fhe_traits::Serialize as FheSerialize; +use crate::util::timeit::timeit; + +use iron::prelude::*; +use iron::status; +use iron::mime::Mime; +use router::Router; +use std::io::Read; + +use crate::enclave_server::models::{Round, Ciphernode, JsonResponse, JsonRequest, RegisterNodeResponse, SKSShareRequest, SKSSharePoll, SKSShareResponse, PKShareCount, PKRequest, GetCiphernode, GetEligibilityRequest, CRPRequest}; +use crate::enclave_server::database::{GLOBAL_DB, get_state, pick_response}; + +pub fn setup_routes(router: &mut Router) { + router.post("/register_ciphernode", register_ciphernode, "register_ciphernode"); + router.post("/get_pk_share_count", get_pk_share_count, "get_pk_share_count"); + router.post("/get_pk_by_round", get_pk_by_round, "get_pk_by_round"); + router.post("/register_sks_share", register_sks_share, "register_sks_share"); + router.post("/get_sks_shares", get_sks_shares, "get_sks_shares"); + router.post("/get_crp_by_round", get_crp_by_round, "get_crp_by_round"); + router.post("/get_node_by_round", get_node_by_round, "get_node_by_round"); + router.post("/get_round_eligibility", get_round_eligibility, "get_round_eligibility"); +} + +#[tokio::main] +async fn register_ciphernode(req: &mut Request) -> IronResult { + let mut payload = String::new(); + + // read the POST body + req.body.read_to_string(&mut payload).unwrap(); + + // we're expecting the POST to match the format of our JsonRequest struct + let incoming: JsonRequest = serde_json::from_str(&payload).unwrap(); + println!("{:?}", incoming.response); + println!("ID: {:?}", incoming.id); + println!("Round ID: {:?}", incoming.round_id); + + let (mut state, key) = get_state(incoming.round_id); + + state.pk_share_count = state.pk_share_count + 1; + state.ciphernode_count = state.ciphernode_count + 1; + let cnode = Ciphernode { + id: incoming.id, + pk_share: incoming.pk_share, + sks_share: vec![0], + }; + state.ciphernodes.push(cnode); + let state_str = serde_json::to_string(&state).unwrap(); + let state_bytes = state_str.into_bytes(); + GLOBAL_DB.insert(key, state_bytes).unwrap(); + + println!("pk share store for node id {:?}", incoming.id); + println!("ciphernode count {:?}", state.ciphernode_count); + println!("ciphernode total {:?}", state.ciphernode_total); + println!("pk share count {:?}", state.pk_share_count); + + if state.ciphernode_count == state.ciphernode_total { + println!("All shares received"); + let _ = aggregate_pk_shares(incoming.round_id).await; + } + + let response = RegisterNodeResponse { + response: "Node Registered".to_string(), + node_index: state.ciphernode_count, + }; + let out = serde_json::to_string(&response).unwrap(); + + let content_type = "application/json".parse::().unwrap(); + Ok(Response::with((content_type, status::Ok, out))) +} + +fn register_sks_share(req: &mut Request) -> IronResult { + let mut payload = String::new(); + + // read the POST body + req.body.read_to_string(&mut payload).unwrap(); + + // we're expecting the POST to match the format of our JsonRequest struct + let incoming: SKSShareRequest = serde_json::from_str(&payload).unwrap(); + println!("{:?}", incoming.response); + println!("Index: {:?}", incoming.index); // cipher node id (based on first upload of pk share) + println!("Round ID: {:?}", incoming.round_id); + + + let mut round_key = incoming.round_id.to_string(); + round_key.push_str("-storage"); + println!("Database key is {:?}", round_key); + + let state_out = GLOBAL_DB.get(round_key.clone()).unwrap().unwrap(); + let state_out_str = str::from_utf8(&state_out).unwrap(); + let mut state_out_struct: Round = serde_json::from_str(&state_out_str).unwrap(); + state_out_struct.sks_share_count = state_out_struct.sks_share_count + 1; + + let index = incoming.index; // TODO use hashmap with node id as key + state_out_struct.ciphernodes[index as usize].sks_share = incoming.sks_share; + let state_str = serde_json::to_string(&state_out_struct).unwrap(); + let state_bytes = state_str.into_bytes(); + GLOBAL_DB.insert(round_key, state_bytes).unwrap(); + println!("sks share stored for node index {:?}", incoming.index); + + // toso get share threshold from client config + if state_out_struct.sks_share_count == state_out_struct.ciphernode_total { + println!("All sks shares received"); + //aggregate_pk_shares(incoming.round_id).await; + // TODO: maybe notify cipher nodes + } + + // create a response with our random string, and pass in the string from the POST body + let response = JsonResponse { response: pick_response() }; + let out = serde_json::to_string(&response).unwrap(); + + let content_type = "application/json".parse::().unwrap(); + Ok(Response::with((content_type, status::Ok, out))) +} + +fn get_sks_shares(req: &mut Request) -> IronResult { + let mut payload = String::new(); + + // read the POST body + req.body.read_to_string(&mut payload).unwrap(); + + // we're expecting the POST to match the format of our JsonRequest struct + let incoming: SKSSharePoll = serde_json::from_str(&payload).unwrap(); + //const length: usize = incoming.cyphernode_count; + + let (mut state, key) = get_state(incoming.round_id); + + let mut shares = Vec::with_capacity(incoming.ciphernode_count as usize); + + // toso get share threshold from client config + if state.sks_share_count == state.ciphernode_total { + println!("All sks shares received... sending to cipher nodes"); + for i in 1..state.ciphernode_total + 1 { + println!("reading share {:?}", i); + shares.push(state.ciphernodes[i as usize].sks_share.clone()); + } + let response = SKSShareResponse { + response: "final".to_string(), + round_id: incoming.round_id, + sks_shares: shares, + }; + state.status = "Finalized".to_string(); + let state_str = serde_json::to_string(&state).unwrap(); + let state_bytes = state_str.into_bytes(); + GLOBAL_DB.insert(key, state_bytes).unwrap(); + let out = serde_json::to_string(&response).unwrap(); + println!("get rounds hit"); + + let content_type = "application/json".parse::().unwrap(); + Ok(Response::with((content_type, status::Ok, out))) + } else { + let response = SKSShareResponse { + response: "waiting".to_string(), + round_id: incoming.round_id, + sks_shares: shares, + }; + let out = serde_json::to_string(&response).unwrap(); + println!("get rounds hit"); + + let content_type = "application/json".parse::().unwrap(); + Ok(Response::with((content_type, status::Ok, out))) + } +} + +fn get_crp_by_round(req: &mut Request) -> IronResult { + let mut payload = String::new(); + // read the POST body + req.body.read_to_string(&mut payload).unwrap(); + let mut incoming: CRPRequest = serde_json::from_str(&payload).unwrap(); + println!("Request crp for round {:?}", incoming.round_id); + + let (state, _key) = get_state(incoming.round_id); + incoming.crp_bytes = state.crp; + let out = serde_json::to_string(&incoming).unwrap(); + + let content_type = "application/json".parse::().unwrap(); + Ok(Response::with((content_type, status::Ok, out))) +} + +fn get_pk_by_round(req: &mut Request) -> IronResult { + let mut payload = String::new(); + // read the POST body + req.body.read_to_string(&mut payload).unwrap(); + let mut incoming: PKRequest = serde_json::from_str(&payload).unwrap(); + + let (state, _key) = get_state(incoming.round_id); + incoming.pk_bytes = state.pk; + let out = serde_json::to_string(&incoming).unwrap(); + + let content_type = "application/json".parse::().unwrap(); + println!("Request for round {:?} public key", incoming.round_id); + Ok(Response::with((content_type, status::Ok, out))) +} + +fn get_pk_share_count(req: &mut Request) -> IronResult { + let mut payload = String::new(); + // read the POST body + req.body.read_to_string(&mut payload).unwrap(); + + let mut incoming: PKShareCount = serde_json::from_str(&payload).unwrap(); + + let (state, _key) = get_state(incoming.round_id); + incoming.share_id = state.pk_share_count; + let out = serde_json::to_string(&incoming).unwrap(); + + let content_type = "application/json".parse::().unwrap(); + Ok(Response::with((content_type, status::Ok, out))) +} + +fn get_round_eligibility(req: &mut Request) -> IronResult { + let mut payload = String::new(); + // read the POST body + req.body.read_to_string(&mut payload).unwrap(); + let mut incoming: GetEligibilityRequest = serde_json::from_str(&payload).unwrap(); + println!("Request node elegibility for round {:?}", incoming.round_id); + + let (state, _key) = get_state(incoming.round_id); + + for i in 1..state.ciphernodes.len() { + println!("checking ciphernode {:?}", i); + println!("server db id {:?}", state.ciphernodes[i as usize].id); + println!("incoming request id {:?}", incoming.node_id); + if state.ciphernodes[i as usize].id == incoming.node_id { + incoming.is_eligible = true; + incoming.reason = "Previously Registered".to_string(); + }; + }; + + if state.ciphernode_total == state.ciphernode_count && incoming.reason != "Previously Registered" { + incoming.is_eligible = false; + incoming.reason = "Round Full".to_string(); + }; + + if state.ciphernode_total > state.ciphernode_count && incoming.reason != "Previously Registered" { + incoming.is_eligible = true; + incoming.reason = "Open Node Spot".to_string(); + }; + + let init_time = Utc::now(); + let timestamp = init_time.timestamp(); + + if timestamp >= (state.start_time + state.poll_length as i64) { + incoming.is_eligible = false; + incoming.reason = "Waiting For New Round".to_string(); + } + + let out = serde_json::to_string(&incoming).unwrap(); + + let content_type = "application/json".parse::().unwrap(); + Ok(Response::with((content_type, status::Ok, out))) +} + +fn get_node_by_round(req: &mut Request) -> IronResult { + let mut payload = String::new(); + // read the POST body + req.body.read_to_string(&mut payload).unwrap(); + let incoming: GetCiphernode = serde_json::from_str(&payload).unwrap(); + println!("Request node data for round {:?}", incoming.round_id); + + let (state, _key) = get_state(incoming.round_id); + let mut cnode = Ciphernode { + id: 0, + pk_share: vec![0], + sks_share: vec![0], + }; + + for i in 0..state.ciphernodes.len() { + if state.ciphernodes[i as usize].id == incoming.ciphernode_id { + cnode.id = state.ciphernodes[i as usize].id; + cnode.pk_share = state.ciphernodes[i as usize].pk_share.clone(); + cnode.sks_share = state.ciphernodes[i as usize].sks_share.clone(); + }; + }; + + if cnode.id != 0 { + let out = serde_json::to_string(&cnode).unwrap(); + + let content_type = "application/json".parse::().unwrap(); + Ok(Response::with((content_type, status::Ok, out))) + } else { + let response = JsonResponse { response: "Ciphernode Not Registered".to_string() }; + let out = serde_json::to_string(&response).unwrap(); + + let content_type = "application/json".parse::().unwrap(); + Ok(Response::with((content_type, status::Ok, out))) + } + + // let response = JsonResponse { response: "Ciphernode Not Registered".to_string() }; + // let out = serde_json::to_string(&response).unwrap(); + + // let content_type = "application/json".parse::().unwrap(); + // Ok(Response::with((content_type, status::Ok, out))) +} + + +async fn aggregate_pk_shares(round_id: u32) -> Result<(), Box> { + println!("aggregating validator keyshare"); + + let degree = 4096; + let plaintext_modulus: u64 = 4096; + let moduli = vec![0xffffee001, 0xffffc4001, 0x1ffffe0001]; + + // Generate a deterministic seed for the Common Poly + //let mut seed = ::Seed::default(); + + // Let's generate the BFV parameters structure. + let params = timeit!( + "Parameters generation", + BfvParametersBuilder::new() + .set_degree(degree) + .set_plaintext_modulus(plaintext_modulus) + .set_moduli(&moduli) + .build_arc()? + ); + + let mut round_key = round_id.to_string(); + round_key.push_str("-storage"); + println!("Database key is {:?}", round_key); + + let state_out = GLOBAL_DB.get(round_key.clone()).unwrap().unwrap(); + let state_out_str = str::from_utf8(&state_out).unwrap(); + let mut state: Round = serde_json::from_str(&state_out_str).unwrap(); + println!("checking db after drop {:?}", state.ciphernode_count); + println!("{:?}", state.ciphernodes[0].id); + //println!("{:?}", state.ciphernodes[0].pk_share); + + //let crp = CommonRandomPoly::new_deterministic(¶ms, seed)?; + let crp = CommonRandomPoly::deserialize(&state.crp, ¶ms)?; + + // Party setup: each party generates a secret key and shares of a collective + // public key. + struct Party { + pk_share: PublicKeyShare, + } + + let mut parties :Vec = Vec::new(); + for i in 1..state.ciphernode_total + 1 { // todo fix init code that causes offset + // read in pk_shares from storage + println!("Aggregating PKShare... id {}", i); + let data_des = PublicKeyShare::deserialize(&state.ciphernodes[i as usize].pk_share, ¶ms, crp.clone()).unwrap(); + // let pk_share = PublicKeyShare::new(&sk_share, crp.clone(), &mut thread_rng())?; + parties.push(Party { pk_share: data_des }); + } + + // Aggregation: this could be one of the parties or a separate entity. Or the + // parties can aggregate cooperatively, in a tree-like fashion. + let pk = timeit!("Public key aggregation", { + let pk: PublicKey = parties.iter().map(|p| p.pk_share.clone()).aggregate()?; + pk + }); + //println!("{:?}", pk); + println!("Multiparty Public Key Generated"); + let store_pk = pk.to_bytes(); + state.pk = store_pk; + let state_str = serde_json::to_string(&state).unwrap(); + let state_bytes = state_str.into_bytes(); + GLOBAL_DB.insert(round_key, state_bytes).unwrap(); + println!("aggregate pk stored for round {:?}", round_id); + Ok(()) +} diff --git a/packages/server/src/enclave_server/routes/index.rs b/packages/server/src/enclave_server/routes/index.rs new file mode 100644 index 0000000..32cf710 --- /dev/null +++ b/packages/server/src/enclave_server/routes/index.rs @@ -0,0 +1,102 @@ +use std::str; +use iron::mime::Mime; +use iron::prelude::*; +use iron::status; +use router::Router; +use std::io::Read; +use jwt::SignWithKey; +use sha2::Sha256; +use std::collections::BTreeMap; +use hmac::{Hmac, Mac}; + +use crate::enclave_server::models::{JsonResponse, AuthenticationLogin, AuthenticationDB, AuthenticationResponse}; +use crate::enclave_server::database::{GLOBAL_DB, pick_response}; + +pub fn setup_routes(router: &mut Router) { + router.get("/", handler, "index"); + router.get("/health", health_handler, "health"); + router.post( + "/authentication_login", + authentication_login, + "authentication_login", + ); +} + +fn handler(_req: &mut Request) -> IronResult { + let response = JsonResponse { + response: pick_response(), + }; + let out = serde_json::to_string(&response).unwrap(); + println!("index handler hit"); + let content_type = "application/json".parse::().unwrap(); + Ok(Response::with((content_type, status::Ok, out))) +} + +fn health_handler(_req: &mut Request) -> IronResult { + let content_type = "application/json".parse::().unwrap(); + Ok(Response::with((content_type, status::Ok))) +} + +fn authentication_login(req: &mut Request) -> IronResult { + let mut payload = String::new(); + // read the POST body + req.body.read_to_string(&mut payload).unwrap(); + let incoming: AuthenticationLogin = serde_json::from_str(&payload).unwrap(); + println!("Twitter Login Request"); + + // hmac + let hmac_key: Hmac = Hmac::new_from_slice(b"some-secret").unwrap(); + let mut claims = BTreeMap::new(); + claims.insert("postId", incoming.postId); + let token_str = claims.sign_with_key(&hmac_key).unwrap(); + + // db + let key = "authentication"; + let mut authsdb = GLOBAL_DB.get(key).unwrap(); + let mut response_str = "".to_string(); + let mut jwt_token = "".to_string(); + + if authsdb == None { + println!("initializing first auth in db"); + // hmac + let auth_struct = AuthenticationDB { + jwt_tokens: vec![token_str.clone()], + }; + let authsdb_str = serde_json::to_string(&auth_struct).unwrap(); + let authsdb_bytes = authsdb_str.into_bytes(); + GLOBAL_DB.insert(key, authsdb_bytes).unwrap(); + // set response + response_str = "Authorized".to_string(); + } else { + // look for previous auth + let mut au_db = authsdb.unwrap(); + let authsdb_out_str = str::from_utf8(&au_db).unwrap(); + let mut authsdb_out_struct: AuthenticationDB = serde_json::from_str(&authsdb_out_str).unwrap(); + + for i in 0..authsdb_out_struct.jwt_tokens.len() { + if authsdb_out_struct.jwt_tokens[i as usize] == token_str { + println!("Found previous login."); + response_str = "Already Authorized".to_string(); + } + }; + + if response_str != "Already Authorized" { + println!("Inserting new login to db."); + authsdb_out_struct.jwt_tokens.push(token_str.clone()); + let authsdb_str = serde_json::to_string(&authsdb_out_struct).unwrap(); + let authsdb_bytes = authsdb_str.into_bytes(); + GLOBAL_DB.insert(key, authsdb_bytes).unwrap(); + response_str = "Authorized".to_string(); + } + }; + + let response = AuthenticationResponse { + response: response_str, + jwt_token: token_str, + }; + let out = serde_json::to_string(&response).unwrap(); + + let content_type = "application/json".parse::().unwrap(); + Ok(Response::with((content_type, status::Ok, out))) +} + diff --git a/packages/server/src/enclave_server/routes/mod.rs b/packages/server/src/enclave_server/routes/mod.rs new file mode 100644 index 0000000..f43213a --- /dev/null +++ b/packages/server/src/enclave_server/routes/mod.rs @@ -0,0 +1,15 @@ +mod index; +mod rounds; +mod ciphernode; +mod voting; +mod state; + +use router::Router; + +pub fn setup_routes(router: &mut Router) { + index::setup_routes(router); + rounds::setup_routes(router); + ciphernode::setup_routes(router); + voting::setup_routes(router); + state::setup_routes(router); +} \ No newline at end of file diff --git a/packages/server/src/enclave_server/routes/rounds.rs b/packages/server/src/enclave_server/routes/rounds.rs new file mode 100644 index 0000000..15b8971 --- /dev/null +++ b/packages/server/src/enclave_server/routes/rounds.rs @@ -0,0 +1,243 @@ +use chrono::Utc; +use fhe::{bfv::BfvParametersBuilder, mbfv::CommonRandomPoly}; +use fhe_traits::Serialize; +use iron::mime::Mime; +use iron::prelude::*; +use iron::status; +use rand::thread_rng; +use router::Router; +use std::env; +use std::io::Read; + +use ethers::{ + providers::{Http, Middleware, Provider}, + types::U64, +}; + +use crate::util::timeit::timeit; + +use crate::enclave_server::database::{generate_emoji, get_state, GLOBAL_DB}; +use crate::enclave_server::models::{ + Ciphernode, CrispConfig, JsonResponse, PollLengthRequest, ReportTallyRequest, Round, + RoundCount, TimestampRequest, +}; + +pub fn setup_routes(router: &mut Router) { + router.get("/get_rounds", get_rounds, "get_rounds"); + router.post("/init_crisp_round", init_crisp_round, "init_crisp_round"); + router.post( + "/get_start_time_by_round", + get_start_time_by_round, + "get_start_time_by_round", + ); + router.post( + "/get_poll_length_by_round", + get_poll_length_by_round, + "get_poll_length_by_round", + ); + router.post("/report_tally", report_tally, "report_tally"); +} + +fn get_rounds(_req: &mut Request) -> IronResult { + //let test = _req.headers.get::().unwrap(); + //println!("content_type: {:?}", test); + + // let test3 = _req.headers.get::>().unwrap(); + // println!("auth: {:?}", test3.token); + // let key: Hmac = Hmac::new_from_slice(b"some-secret").unwrap(); + // let claims: BTreeMap = test3.token.verify_with_key(&key).unwrap(); + // println!("decoded hmac {:?}", claims); + + //let test2 = _req.headers.get::(); + //println!("user agent: {:?}", test2); + + let key = "round_count"; + let mut round = GLOBAL_DB.get(key).unwrap(); + if round == None { + println!("initializing first round in db"); + GLOBAL_DB.insert(key, b"0".to_vec()).unwrap(); + round = GLOBAL_DB.get(key).unwrap(); + } + let round_key = std::str::from_utf8(round.unwrap().as_ref()) + .unwrap() + .to_string(); + let round_int = round_key.parse::().unwrap(); + + let count = RoundCount { + round_count: round_int, + }; + println!("round_count: {:?}", count.round_count); + + let out = serde_json::to_string(&count).unwrap(); + println!("get rounds hit"); + + let content_type = "application/json".parse::().unwrap(); + Ok(Response::with((content_type, status::Ok, out))) +} + +#[tokio::main] +async fn init_crisp_round(req: &mut Request) -> IronResult { + // let auth = _req.headers.get::>().unwrap(); + // if auth.token != env { + + // } + println!("generating round crp"); + + let infura_val = env!("INFURAKEY"); + let mut rpc_url = "https://sepolia.infura.io/v3/".to_string(); + rpc_url.push_str(&infura_val); + + let provider = Provider::::try_from(rpc_url.clone()).unwrap(); + let block_number: U64 = provider.get_block_number().await.unwrap(); + + let degree = 4096; + let plaintext_modulus: u64 = 4096; + let moduli = vec![0xffffee001, 0xffffc4001, 0x1ffffe0001]; + + // Let's generate the BFV parameters structure. + let params = timeit!( + "Parameters generation", + BfvParametersBuilder::new() + .set_degree(degree) + .set_plaintext_modulus(plaintext_modulus) + .set_moduli(&moduli) + .build_arc() + .unwrap() + ); + let crp = CommonRandomPoly::new(¶ms, &mut thread_rng()).unwrap(); + let crp_bytes = crp.to_bytes(); + + let mut payload = String::new(); + + // read the POST body + req.body.read_to_string(&mut payload).unwrap(); + + // we're expecting the POST to match the format of our JsonRequest struct + let incoming: CrispConfig = serde_json::from_str(&payload).unwrap(); + println!("ID: {:?}", incoming.round_id); // TODO: check that client sent the expected next round_id + println!("Address: {:?}", incoming.voting_address); + + // -------------- + let key = "round_count"; + //db.remove(key)?; + let round = GLOBAL_DB.get(key).unwrap(); + if round == None { + println!("initializing first round in db"); + GLOBAL_DB.insert(key, b"0".to_vec()).unwrap(); + } + let round_key = std::str::from_utf8(round.unwrap().as_ref()) + .unwrap() + .to_string(); + let mut round_int = round_key.parse::().unwrap(); + round_int = round_int + 1; + let mut inc_round_key = round_int.to_string(); + inc_round_key.push_str("-storage"); + println!( + "Database key is {:?} and round int is {:?}", + inc_round_key, round_int + ); + + let init_time = Utc::now(); + let timestamp = init_time.timestamp(); + println!("timestamp {:?}", timestamp); + + let (emoji1, emoji2) = generate_emoji(); + + let state = Round { + id: round_int, + status: "Active".to_string(), + poll_length: incoming.poll_length, + voting_address: incoming.voting_address, + chain_id: incoming.chain_id, + ciphernode_count: 0, + pk_share_count: 0, + sks_share_count: 0, + vote_count: 0, + crp: crp_bytes, + pk: vec![0], + start_time: timestamp, + block_start: block_number, + ciphernode_total: incoming.ciphernode_count, + emojis: [emoji1, emoji2], + votes_option_1: 0, + votes_option_2: 0, + ciphernodes: vec![Ciphernode { + id: 0, + pk_share: vec![0], + sks_share: vec![0], + }], + has_voted: vec!["".to_string()], + }; + + let state_str = serde_json::to_string(&state).unwrap(); + let state_bytes = state_str.into_bytes(); + let key2 = round_int.to_string(); + GLOBAL_DB.insert(inc_round_key, state_bytes).unwrap(); + + let new_round_bytes = key2.into_bytes(); + GLOBAL_DB.insert(key, new_round_bytes).unwrap(); + + // create a response with our random string, and pass in the string from the POST body + let response = JsonResponse { + response: "CRISP Initiated".to_string(), + }; + let out = serde_json::to_string(&response).unwrap(); + + let content_type = "application/json".parse::().unwrap(); + Ok(Response::with((content_type, status::Ok, out))) +} + +fn get_start_time_by_round(req: &mut Request) -> IronResult { + let mut payload = String::new(); + // read the POST body + req.body.read_to_string(&mut payload).unwrap(); + let mut incoming: TimestampRequest = serde_json::from_str(&payload).unwrap(); + println!("Request start time for round {:?}", incoming.round_id); + + let (state, _key) = get_state(incoming.round_id); + incoming.timestamp = state.start_time; + let out = serde_json::to_string(&incoming).unwrap(); + + let content_type = "application/json".parse::().unwrap(); + Ok(Response::with((content_type, status::Ok, out))) +} + +fn get_poll_length_by_round(req: &mut Request) -> IronResult { + let mut payload = String::new(); + // read the POST body + req.body.read_to_string(&mut payload).unwrap(); + let mut incoming: PollLengthRequest = serde_json::from_str(&payload).unwrap(); + println!("Request poll length for round {:?}", incoming.round_id); + + let (state, _key) = get_state(incoming.round_id); + incoming.poll_length = state.poll_length; + let out = serde_json::to_string(&incoming).unwrap(); + + let content_type = "application/json".parse::().unwrap(); + Ok(Response::with((content_type, status::Ok, out))) +} + +fn report_tally(req: &mut Request) -> IronResult { + let mut payload = String::new(); + // read the POST body + req.body.read_to_string(&mut payload).unwrap(); + let incoming: ReportTallyRequest = serde_json::from_str(&payload).unwrap(); + println!("Request report tally for round {:?}", incoming.round_id); + + let (mut state, key) = get_state(incoming.round_id); + if state.votes_option_1 == 0 && state.votes_option_2 == 0 { + state.votes_option_1 = incoming.option_1; + state.votes_option_2 = incoming.option_2; + + let state_str = serde_json::to_string(&state).unwrap(); + let state_bytes = state_str.into_bytes(); + GLOBAL_DB.insert(key, state_bytes).unwrap(); + } + let response = JsonResponse { + response: "Tally Reported".to_string(), + }; + let out = serde_json::to_string(&response).unwrap(); + + let content_type = "application/json".parse::().unwrap(); + Ok(Response::with((content_type, status::Ok, out))) +} diff --git a/packages/server/src/enclave_server/routes/state.rs b/packages/server/src/enclave_server/routes/state.rs new file mode 100644 index 0000000..bd49a6c --- /dev/null +++ b/packages/server/src/enclave_server/routes/state.rs @@ -0,0 +1,148 @@ +use iron::prelude::*; +use iron::status; +use iron::mime::Mime; +use router::Router; +use std::io::Read; + + +use crate::enclave_server::models::{GetRoundRequest, WebResultRequest, AllWebStates, StateLite, StateWeb}; +use crate::enclave_server::database::{get_state, get_round_count}; + + +pub fn setup_routes(router: &mut Router) { + router.get("/get_web_result_all", get_web_result_all, "get_web_result_all"); + router.post("/get_round_state_lite", get_round_state_lite, "get_round_state_lite"); + router.post("/get_round_state", get_round_state, "get_round_state"); + router.post("/get_web_result", get_web_result, "get_web_result"); + router.post("/get_round_state_web", get_round_state_web, "get_round_state_web"); +} + +fn get_web_result(req: &mut Request) -> IronResult { + let mut payload = String::new(); + // read the POST body + req.body.read_to_string(&mut payload).unwrap(); + let incoming: GetRoundRequest = serde_json::from_str(&payload).unwrap(); + println!("Request web state for round {:?}", incoming.round_id); + + let (state, _key) = get_state(incoming.round_id); + + let response = WebResultRequest { + round_id: incoming.round_id, + option_1_tally: state.votes_option_1, + option_2_tally: state.votes_option_2, + total_votes: state.votes_option_1 + state.votes_option_2, + option_1_emoji: state.emojis[0].clone(), + option_2_emoji: state.emojis[1].clone(), + end_time: state.start_time + state.poll_length as i64 + }; + + let out = serde_json::to_string(&response).unwrap(); + + let content_type = "application/json".parse::().unwrap(); + Ok(Response::with((content_type, status::Ok, out))) +} + +fn get_web_result_all(_req: &mut Request) -> IronResult { + println!("Request all web state."); + + let round_count = get_round_count(); + let mut states: Vec = Vec::with_capacity(round_count as usize); + + for i in 1..round_count { + let (state, _key) = get_state(i); + let web_state = WebResultRequest { + round_id: i, + option_1_tally: state.votes_option_1, + option_2_tally: state.votes_option_2, + total_votes: state.votes_option_1 + state.votes_option_2, + option_1_emoji: state.emojis[0].clone(), + option_2_emoji: state.emojis[1].clone(), + end_time: state.start_time + state.poll_length as i64 + }; + states.push(web_state); + } + + let response = AllWebStates { states: states }; + let out = serde_json::to_string(&response).unwrap(); + + let content_type = "application/json".parse::().unwrap(); + Ok(Response::with((content_type, status::Ok, out))) +} + + + +fn get_round_state(req: &mut Request) -> IronResult { + let mut payload = String::new(); + // read the POST body + req.body.read_to_string(&mut payload).unwrap(); + let incoming: GetRoundRequest = serde_json::from_str(&payload).unwrap(); + println!("Request state for round {:?}", incoming.round_id); + + let (state, _key) = get_state(incoming.round_id); + let out = serde_json::to_string(&state).unwrap(); + + let content_type = "application/json".parse::().unwrap(); + Ok(Response::with((content_type, status::Ok, out))) +} + +fn get_round_state_web(req: &mut Request) -> IronResult { + let mut payload = String::new(); + // read the POST body + req.body.read_to_string(&mut payload).unwrap(); + let incoming: GetRoundRequest = serde_json::from_str(&payload).unwrap(); + println!("Request state for round {:?}", incoming.round_id); + + let (state, _key) = get_state(incoming.round_id); + let state_lite = StateWeb { + id: state.id, + status: state.status, + poll_length: state.poll_length, + voting_address: state.voting_address, + chain_id: state.chain_id, + ciphernode_count: state.ciphernode_count, + pk_share_count: state.pk_share_count, + sks_share_count: state.sks_share_count, + vote_count: state.vote_count, + start_time: state.start_time, + ciphernode_total: state.ciphernode_total, + emojis: state.emojis, + }; + + let out = serde_json::to_string(&state_lite).unwrap(); + + let content_type = "application/json".parse::().unwrap(); + Ok(Response::with((content_type, status::Ok, out))) +} + + +fn get_round_state_lite(req: &mut Request) -> IronResult { + let mut payload = String::new(); + // read the POST body + req.body.read_to_string(&mut payload).unwrap(); + let incoming: GetRoundRequest = serde_json::from_str(&payload).unwrap(); + println!("Request state for round {:?}", incoming.round_id); + + let (state, _key) = get_state(incoming.round_id); + let state_lite = StateLite { + id: state.id, + status: state.status, + poll_length: state.poll_length, + voting_address: state.voting_address, + chain_id: state.chain_id, + ciphernode_count: state.ciphernode_count, + pk_share_count: state.pk_share_count, + sks_share_count: state.sks_share_count, + vote_count: state.vote_count, + crp: state.crp, + pk: state.pk, + start_time: state.start_time, + block_start: state.block_start, + ciphernode_total: state.ciphernode_total, + emojis: state.emojis, + }; + + let out = serde_json::to_string(&state_lite).unwrap(); + + let content_type = "application/json".parse::().unwrap(); + Ok(Response::with((content_type, status::Ok, out))) +} diff --git a/packages/server/src/enclave_server/routes/voting.rs b/packages/server/src/enclave_server/routes/voting.rs new file mode 100644 index 0000000..a92efbf --- /dev/null +++ b/packages/server/src/enclave_server/routes/voting.rs @@ -0,0 +1,144 @@ + +use std::{env, sync::Arc, str}; +use iron::prelude::*; +use iron::status; +use iron::mime::Mime; +use router::Router; +use std::io::Read; +use ethers::{ + prelude::abigen, + providers::{Http, Provider, Middleware}, + middleware::{SignerMiddleware, MiddlewareBuilder}, + signers::{LocalWallet, Signer}, + types::{Address, Bytes, TxHash, BlockNumber}, +}; + + +use crate::enclave_server::models::{EncryptedVote, JsonResponseTxHash, GetEmojisRequest, VoteCountRequest}; +use crate::enclave_server::database::{GLOBAL_DB, get_state}; + + +pub fn setup_routes(router: &mut Router) { + router.post("/broadcast_enc_vote", broadcast_enc_vote, "broadcast_enc_vote"); + router.post("/get_vote_count_by_round", get_vote_count_by_round, "get_vote_count_by_round"); + router.post("/get_emojis_by_round", get_emojis_by_round, "get_emojis_by_round"); +} + +#[tokio::main] +async fn broadcast_enc_vote(req: &mut Request) -> IronResult { + let mut payload = String::new(); + // read the POST body + req.body.read_to_string(&mut payload).unwrap(); + let incoming: EncryptedVote = serde_json::from_str(&payload).unwrap(); + let mut response_str = ""; + let mut converter = "".to_string(); + let (mut state, key) = get_state(incoming.round_id); + + for i in 0..state.has_voted.len() { + if state.has_voted[i] == incoming.postId { + response_str = "User Has Already Voted"; + } else { + response_str = "Vote Successful"; + } + }; + + if response_str == "Vote Successful" { + let sol_vote = Bytes::from(incoming.enc_vote_bytes); + let tx_hash = call_contract(sol_vote, state.voting_address.clone()).await.unwrap(); + converter = "0x".to_string(); + for i in 0..32 { + if tx_hash[i] <= 16 { + converter.push_str("0"); + converter.push_str(&format!("{:x}", tx_hash[i])); + } else { + converter.push_str(&format!("{:x}", tx_hash[i])); + } + } + + state.vote_count = state.vote_count + 1; + state.has_voted.push(incoming.postId); + let state_str = serde_json::to_string(&state).unwrap(); + let state_bytes = state_str.into_bytes(); + GLOBAL_DB.insert(key, state_bytes).unwrap(); + }; + + let response = JsonResponseTxHash { response: response_str.to_string(), tx_hash: converter }; + let out = serde_json::to_string(&response).unwrap(); + + let content_type = "application/json".parse::().unwrap(); + println!("Request for round {:?} send vote tx", incoming.round_id); + Ok(Response::with((content_type, status::Ok, out))) +} + + +fn get_emojis_by_round(req: &mut Request) -> IronResult { + let mut payload = String::new(); + // read the POST body + req.body.read_to_string(&mut payload).unwrap(); + let mut incoming: GetEmojisRequest = serde_json::from_str(&payload).unwrap(); + println!("Request emojis for round {:?}", incoming.round_id); + + let (state, _key) = get_state(incoming.round_id); + incoming.emojis = state.emojis; + let out = serde_json::to_string(&incoming).unwrap(); + + let content_type = "application/json".parse::().unwrap(); + Ok(Response::with((content_type, status::Ok, out))) +} + +fn get_vote_count_by_round(req: &mut Request) -> IronResult { + let mut payload = String::new(); + // read the POST body + req.body.read_to_string(&mut payload).unwrap(); + let mut incoming: VoteCountRequest = serde_json::from_str(&payload).unwrap(); + println!("Request vote count for round {:?}", incoming.round_id); + + let (state, _key) = get_state(incoming.round_id); + incoming.vote_count = state.vote_count; + let out = serde_json::to_string(&incoming).unwrap(); + + let content_type = "application/json".parse::().unwrap(); + Ok(Response::with((content_type, status::Ok, out))) +} + +async fn call_contract(enc_vote: Bytes, address: String) -> Result> { + println!("calling voting contract"); + + let infura_val = env!("INFURAKEY"); + let mut rpc_url = "https://sepolia.infura.io/v3/".to_string(); + rpc_url.push_str(&infura_val); + + let provider = Provider::::try_from(rpc_url.clone())?; + // let block_number: U64 = provider.get_block_number().await?; + // println!("{block_number}"); + abigen!( + IVOTE, + r#"[ + function voteEncrypted(bytes memory _encVote) public + function getVote(address id) public returns(bytes memory) + event Transfer(address indexed from, address indexed to, uint256 value) + ]"#, + ); + + //const RPC_URL: &str = "https://eth.llamarpc.com"; + let vote_address: &str = &address; + + let eth_val = env!("PRIVATEKEY"); + let wallet: LocalWallet = eth_val + .parse::().unwrap() + .with_chain_id(11155111 as u64); + + let nonce_manager = provider.clone().nonce_manager(wallet.address()); + let curr_nonce = nonce_manager + .get_transaction_count(wallet.address(), Some(BlockNumber::Pending.into())) + .await? + .as_u64(); + + let client = SignerMiddleware::new(provider.clone(), wallet.clone()); + let address: Address = vote_address.parse()?; + let contract = IVOTE::new(address, Arc::new(client.clone())); + + let test = contract.vote_encrypted(enc_vote).nonce(curr_nonce).send().await?.clone(); + println!("{:?}", test); + Ok(test) +} diff --git a/packages/server/src/lib.rs b/packages/server/src/lib.rs new file mode 100644 index 0000000..2fd661a --- /dev/null +++ b/packages/server/src/lib.rs @@ -0,0 +1,3 @@ +pub mod cli; +pub mod util; +pub mod enclave_server; \ No newline at end of file diff --git a/packages/server/src/main.rs b/packages/server/src/main.rs index 622a580..c1e5c88 100644 --- a/packages/server/src/main.rs +++ b/packages/server/src/main.rs @@ -1,4 +1,3 @@ fn main() -> Result<(), ()> { - Ok(()) } diff --git a/packages/server/src/bin/util.rs b/packages/server/src/util.rs similarity index 98% rename from packages/server/src/bin/util.rs rename to packages/server/src/util.rs index 8e3d167..aec57a3 100644 --- a/packages/server/src/bin/util.rs +++ b/packages/server/src/util.rs @@ -28,7 +28,7 @@ pub mod timeit { #[allow(unused_macros)] macro_rules! timeit { ($name:expr, $code:expr) => {{ - use util::DisplayDuration; + use crate::util::DisplayDuration; let start = std::time::Instant::now(); let r = $code; println!("⏱ {}: {}", $name, DisplayDuration(start.elapsed()));