migrated keys to the solana sdk
This commit is contained in:
		
							parent
							
								
									248c2f694c
								
							
						
					
					
						commit
						01b889d273
					
				
							
								
								
									
										2286
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										2286
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @ -4,16 +4,17 @@ version = "0.1.0" | ||||
| edition = "2021" | ||||
| 
 | ||||
| [dependencies] | ||||
| actix-web = "4.9.0" | ||||
| async-stream = "0.3.5" | ||||
| dashmap = "6.0.1" | ||||
| ed25519-dalek = { version = "2.1.1", features = ["rand_core", "serde"] } | ||||
| hex = "0.4.3" | ||||
| prost = "0.13.1" | ||||
| prost-types = "0.13.1" | ||||
| rand = "0.8.5" | ||||
| salvo = { version = "0.70.0", features = ["affix"] } | ||||
| serde = { version = "1.0.210", features = ["derive"] } | ||||
| solana-sdk = "2.0.9" | ||||
| tabled = "0.16.0" | ||||
| tokio = { version = "1.39.2", features = ["macros"] } | ||||
| tokio = { version = "1.39.2", features = ["fs", "macros", "rt-multi-thread"] } | ||||
| tokio-stream = { version = "0.1.15", features = ["sync"] } | ||||
| tonic = "0.12.1" | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										3
									
								
								rustfmt.toml
									
									
									
									
									
										Normal file
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										3
									
								
								rustfmt.toml
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,3 @@ | ||||
| reorder_impl_items = true | ||||
| use_small_heuristics = "Max" | ||||
| merge_imports = true | ||||
							
								
								
									
										236
									
								
								src/datastore.rs
									
									
									
									
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										236
									
								
								src/datastore.rs
									
									
									
									
									
								
							| @ -1,16 +1,18 @@ | ||||
| use crate::grpc::challenge::NodeUpdate; | ||||
| use crate::persistence::FileManager; | ||||
| use crate::{grpc::challenge::NodeUpdate, persistence::FileManager}; | ||||
| use dashmap::{DashMap, DashSet}; | ||||
| use ed25519_dalek::{Signer, SigningKey, VerifyingKey, PUBLIC_KEY_LENGTH}; | ||||
| use rand::rngs::OsRng; | ||||
| use std::time::Duration; | ||||
| use std::time::SystemTime; | ||||
| use std::time::UNIX_EPOCH; | ||||
| use solana_sdk::{ | ||||
|     pubkey::{ParsePubkeyError, Pubkey}, | ||||
|     signature::{keypair::Keypair, Signer}, | ||||
| }; | ||||
| use std::{ | ||||
|     str::FromStr, | ||||
|     time::{Duration, SystemTime, UNIX_EPOCH}, | ||||
| }; | ||||
| use tabled::{Table, Tabled}; | ||||
| 
 | ||||
| #[derive(Clone, PartialEq, Debug)] | ||||
| pub struct NodeInfo { | ||||
|     pub pubkey: VerifyingKey, | ||||
|     pub pubkey: Pubkey, | ||||
|     pub updated_at: SystemTime, | ||||
|     pub public: bool, | ||||
| } | ||||
| @ -19,9 +21,10 @@ pub struct NodeInfo { | ||||
| pub struct Store { | ||||
|     nodes: DashMap<IP, NodeInfo>, | ||||
|     conns: DashSet<IP>, | ||||
|     keys: DashMap<VerifyingKey, SigningKey>, | ||||
|     keys: DashMap<Pubkey, Keypair>, | ||||
|     persistence: FileManager, | ||||
| } | ||||
| #[derive(Debug)] | ||||
| pub enum SigningError { | ||||
|     CorruptedKey, | ||||
|     KeyNotFound, | ||||
| @ -33,8 +36,8 @@ impl From<hex::FromHexError> for SigningError { | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl From<ed25519_dalek::ed25519::Error> for SigningError { | ||||
|     fn from(_: ed25519_dalek::ed25519::Error) -> Self { | ||||
| impl From<ParsePubkeyError> for SigningError { | ||||
|     fn from(_: ParsePubkeyError) -> Self { | ||||
|         Self::CorruptedKey | ||||
|     } | ||||
| } | ||||
| @ -91,23 +94,14 @@ impl Store { | ||||
|             public: bool, | ||||
|         } | ||||
|         let mut output = vec![]; | ||||
|         for (ip, node_info) in self | ||||
|             .nodes | ||||
|             .iter() | ||||
|             .map(|n| (n.key().clone(), n.value().clone())) | ||||
|         { | ||||
|             let pubkey = hex::encode(node_info.pubkey.as_bytes()); | ||||
|         for (ip, node_info) in self.nodes.iter().map(|n| (n.key().clone(), n.value().clone())) { | ||||
|             let pubkey = node_info.pubkey.to_string(); | ||||
|             let age = SystemTime::now() | ||||
|                 .duration_since(node_info.updated_at) | ||||
|                 .unwrap_or(Duration::ZERO) | ||||
|                 .as_secs(); | ||||
|             let public = node_info.public; | ||||
|             output.push(OutputRow { | ||||
|                 ip, | ||||
|                 pubkey, | ||||
|                 age, | ||||
|                 public, | ||||
|             }); | ||||
|             output.push(OutputRow { ip, pubkey, age, public }); | ||||
|         } | ||||
|         Table::new(output).to_string() | ||||
|     } | ||||
| @ -122,32 +116,18 @@ impl Store { | ||||
|             timestamp: String, | ||||
|         } | ||||
|         let mut output = vec![]; | ||||
|         for (ip, keypair, timestamp) in self | ||||
|             .persistence | ||||
|             .get_page_of_20(offset) | ||||
|             .await | ||||
|             .unwrap() | ||||
|             .iter() | ||||
|             .map(|n| { | ||||
|         for (ip, keypair, timestamp) in | ||||
|             self.persistence.get_page_of_20(offset).await.unwrap().iter().map(|n| { | ||||
|                 ( | ||||
|                     n.ip.to_string(), | ||||
|                     n.keypair.clone(), | ||||
|                     n.joined_at | ||||
|                         .duration_since(UNIX_EPOCH) | ||||
|                         .unwrap() | ||||
|                         .as_secs() | ||||
|                         .to_string(), | ||||
|                     Keypair::from_bytes(&n.keypair.to_bytes()).unwrap(), | ||||
|                     n.joined_at.duration_since(UNIX_EPOCH).unwrap().as_secs().to_string(), | ||||
|                 ) | ||||
|             }) | ||||
|         { | ||||
|             let id = offset; | ||||
|             let pubkey = hex::encode(keypair.verifying_key().as_bytes()); | ||||
|             output.push(OutputRow { | ||||
|                 id, | ||||
|                 ip, | ||||
|                 pubkey, | ||||
|                 timestamp, | ||||
|             }); | ||||
|             let pubkey = keypair.pubkey().to_string(); | ||||
|             output.push(OutputRow { id, ip, pubkey, timestamp }); | ||||
|             offset += 1; | ||||
|         } | ||||
|         Table::new(output).to_string() | ||||
| @ -158,13 +138,10 @@ impl Store { | ||||
|         message: &str, | ||||
|         key_id: u64, | ||||
|     ) -> Result<String, SigningError> { | ||||
|         let crate::persistence::Node{keypair, ..} = | ||||
|         let crate::persistence::Node { keypair, .. } = | ||||
|             self.persistence.get_node_by_id(key_id).await?; | ||||
| 
 | ||||
|         // let signature = format!("{:?}", signing_key.sign(message.as_bytes()));
 | ||||
|         let signature = hex::encode(keypair.sign(message.as_bytes()).to_bytes()); | ||||
| 
 | ||||
|         Ok(signature) | ||||
|         let signature = keypair.sign_message(message.as_bytes()); | ||||
|         Ok(signature.to_string()) | ||||
|     } | ||||
| 
 | ||||
|     pub async fn sign_message_with_key( | ||||
| @ -172,31 +149,26 @@ impl Store { | ||||
|         message: &str, | ||||
|         pubkey: &str, | ||||
|     ) -> Result<String, SigningError> { | ||||
|         let mut pubkey_bytes = [0u8; PUBLIC_KEY_LENGTH]; | ||||
|         hex::decode_to_slice(pubkey, &mut pubkey_bytes)?; | ||||
|         let pubkey = VerifyingKey::from_bytes(&pubkey_bytes)?; | ||||
|         let pubkey = Pubkey::from_str(&pubkey)?; | ||||
| 
 | ||||
|         let signing_key = match self.get_privkey(&pubkey).await { | ||||
|         let keypair = match self.get_keypair(&pubkey).await { | ||||
|             Some(k) => k, | ||||
|             None => return Err(SigningError::KeyNotFound), | ||||
|         }; | ||||
| 
 | ||||
|         // let signature = format!("{:?}", signing_key.sign(message.as_bytes()));
 | ||||
|         let signature = hex::encode(signing_key.sign(message.as_bytes()).to_bytes()); | ||||
| 
 | ||||
|         Ok(signature) | ||||
|         let signature = keypair.sign_message(message.as_bytes()); | ||||
|         Ok(signature.to_string()) | ||||
|     } | ||||
| 
 | ||||
|     pub async fn add_key(&self, pubkey: VerifyingKey, privkey: SigningKey) { | ||||
|         self.keys.insert(pubkey, privkey); | ||||
|     pub async fn add_key(&self, pubkey: Pubkey, keypair: Keypair) { | ||||
|         self.keys.insert(pubkey, keypair); | ||||
|     } | ||||
| 
 | ||||
|     pub async fn remove_key(&self, pubkey: &VerifyingKey) { | ||||
|     pub async fn remove_key(&self, pubkey: &Pubkey) { | ||||
|         self.keys.remove(pubkey); | ||||
|     } | ||||
| 
 | ||||
|     async fn get_privkey(&self, pubkey: &VerifyingKey) -> Option<SigningKey> { | ||||
|         self.keys.get(pubkey).map(|k| k.value().clone()) | ||||
|     async fn get_keypair(&self, pubkey: &Pubkey) -> Option<Keypair> { | ||||
|         self.keys.get(pubkey).map(|k| Keypair::from_bytes(&k.to_bytes()).unwrap()) | ||||
|     } | ||||
| 
 | ||||
|     /// This returns true if NodeInfo got modified.
 | ||||
| @ -204,15 +176,14 @@ impl Store { | ||||
|     /// On a side note, there are two types of people in this world:
 | ||||
|     ///     1. Those that can extrapolate... WAT?
 | ||||
|     pub async fn process_node_update(&self, node: NodeUpdate) -> bool { | ||||
|         let key_bytes = match hex::decode(node.keypair.clone()) { | ||||
|             Ok(k) => k, | ||||
|             Err(_) => return false, | ||||
|         }; | ||||
|         let privkey = SigningKey::from_bytes(match &key_bytes.as_slice().try_into() { | ||||
|             Ok(p) => p, | ||||
|             Err(_) => return false, | ||||
|         }); | ||||
|         let pubkey = privkey.verifying_key(); | ||||
|         // solana-sdk is great; it panics if the base58 string is corrupted
 | ||||
|         // we wrap this in catch_unwind() to make sure it doesn't crash the app
 | ||||
|         let keypair = | ||||
|             match std::panic::catch_unwind(|| Keypair::from_base58_string(&node.keypair.clone())) { | ||||
|                 Ok(k) => k, | ||||
|                 Err(_) => return false, | ||||
|             }; | ||||
|         let pubkey = keypair.pubkey(); | ||||
| 
 | ||||
|         // TODO: check this suggestion
 | ||||
|         // let updated_at_std = node
 | ||||
| @ -223,19 +194,13 @@ impl Store { | ||||
|         let updated_at_std: SystemTime = match node.updated_at { | ||||
|             Some(ts) => { | ||||
|                 let duration = Duration::new(ts.seconds as u64, ts.nanos as u32); | ||||
|                 UNIX_EPOCH | ||||
|                     .checked_add(duration) | ||||
|                     .unwrap_or(SystemTime::now()) | ||||
|                 UNIX_EPOCH.checked_add(duration).unwrap_or(SystemTime::now()) | ||||
|             } | ||||
|             None => SystemTime::now(), | ||||
|         }; | ||||
| 
 | ||||
|         self.add_key(pubkey, privkey.clone()).await; | ||||
|         let node_info = NodeInfo { | ||||
|             pubkey, | ||||
|             updated_at: updated_at_std, | ||||
|             public: node.public, | ||||
|         }; | ||||
|         self.add_key(pubkey, Keypair::from_bytes(&keypair.to_bytes()).unwrap()).await; | ||||
|         let node_info = NodeInfo { pubkey, updated_at: updated_at_std, public: node.public }; | ||||
|         if let Some(mut old_node_info) = self.update_node(node.ip.clone(), node_info.clone()).await | ||||
|         { | ||||
|             if !node_info.public { | ||||
| @ -249,7 +214,7 @@ impl Store { | ||||
|                 false => false, | ||||
|             } | ||||
|         } else { | ||||
|             if let Ok(persistence_node) = (node.ip.as_str(), privkey, updated_at_std).try_into() { | ||||
|             if let Ok(persistence_node) = (node.ip.as_str(), keypair, updated_at_std).try_into() { | ||||
|                 if let Err(e) = self.persistence.append_node(persistence_node).await { | ||||
|                     println!("Could not save data to disk: {e}."); | ||||
|                 } | ||||
| @ -271,10 +236,8 @@ impl Store { | ||||
|     pub async fn remove_inactive_nodes(&self) { | ||||
|         let mut dangling_pubkeys = Vec::new(); | ||||
|         self.nodes.retain(|_, v| { | ||||
|           let age = SystemTime::now() | ||||
|                 .duration_since(v.updated_at) | ||||
|                 .unwrap_or(Duration::ZERO) | ||||
|                 .as_secs(); | ||||
|             let age = | ||||
|                 SystemTime::now().duration_since(v.updated_at).unwrap_or(Duration::ZERO).as_secs(); | ||||
|             if age > 120 { | ||||
|                 dangling_pubkeys.push(v.pubkey.clone()); | ||||
|                 false | ||||
| @ -290,10 +253,10 @@ impl Store { | ||||
|     pub async fn get_localhost(&self) -> NodeUpdate { | ||||
|         // TODO trigger reset_localhost_keys on error instead of expects
 | ||||
|         let node = self.nodes.get("localhost").expect("no localhost node"); | ||||
|         let key = self.keys.get(&node.pubkey).expect("no localhost key"); | ||||
|         let keypair = self.keys.get(&node.pubkey).expect("no localhost key"); | ||||
|         NodeUpdate { | ||||
|             ip: "localhost".to_string(), | ||||
|             keypair: hex::encode(key.value().as_bytes()), | ||||
|             keypair: keypair.to_base58_string(), | ||||
|             updated_at: Some(prost_types::Timestamp::from(node.value().updated_at)), | ||||
|             public: false, | ||||
|         } | ||||
| @ -301,56 +264,39 @@ impl Store { | ||||
| 
 | ||||
|     /// refreshes the keys of the node and returns a protobuf for the network
 | ||||
|     pub async fn reset_localhost_keys(&self) -> NodeUpdate { | ||||
|         let mut csprng = OsRng; | ||||
|         let keypair_raw = SigningKey::generate(&mut csprng); | ||||
|         let keypair = hex::encode(keypair_raw.as_bytes()); | ||||
|         let pubkey = keypair_raw.verifying_key(); | ||||
|         let keypair_raw = Keypair::new(); | ||||
|         let keypair = keypair_raw.to_base58_string(); | ||||
|         let pubkey = keypair_raw.pubkey(); | ||||
|         let ip = "localhost".to_string(); | ||||
|         let updated_at = SystemTime::now(); | ||||
|         let public = false; | ||||
|         self.add_key(pubkey, keypair_raw.clone()).await; | ||||
|         if let Some(old_data) = self | ||||
|             .update_node( | ||||
|                 ip.clone(), | ||||
|                 NodeInfo { | ||||
|                     pubkey, | ||||
|                     updated_at, | ||||
|                     public, | ||||
|                 }, | ||||
|             ) | ||||
|             .await | ||||
|         self.add_key(pubkey, keypair_raw).await; | ||||
|         if let Some(old_data) = | ||||
|             self.update_node(ip.clone(), NodeInfo { pubkey, updated_at, public }).await | ||||
|         { | ||||
|             self.remove_key(&old_data.pubkey).await; | ||||
|         }; | ||||
|         let updated_at = Some(prost_types::Timestamp::from(updated_at)); | ||||
|         NodeUpdate { | ||||
|             ip, | ||||
|             keypair, | ||||
|             updated_at, | ||||
|             public, | ||||
|         } | ||||
|         NodeUpdate { ip, keypair, updated_at, public } | ||||
|     } | ||||
| 
 | ||||
|     pub async fn get_full_node_list(&self) -> Vec<NodeUpdate> { | ||||
|         self.nodes | ||||
|             .iter() | ||||
|             .filter_map(|node| { | ||||
|                 self.keys | ||||
|                     .get(&node.value().pubkey) | ||||
|                     .map(|signing_key| NodeUpdate { | ||||
|                         ip: node.key().to_string(), | ||||
|                         keypair: hex::encode(signing_key.as_bytes()), | ||||
|                         updated_at: Some(prost_types::Timestamp::from(node.value().updated_at)), | ||||
|                         public: node.value().public, | ||||
|                     }) | ||||
|                 self.keys.get(&node.value().pubkey).map(|keypair| NodeUpdate { | ||||
|                     ip: node.key().to_string(), | ||||
|                     keypair: keypair.to_base58_string(), | ||||
|                     updated_at: Some(prost_types::Timestamp::from(node.value().updated_at)), | ||||
|                     public: node.value().public, | ||||
|                 }) | ||||
|             }) | ||||
|             .collect() | ||||
|     } | ||||
| 
 | ||||
|     // returns a random node that does not have an active connection
 | ||||
|     pub async fn get_random_node(&self) -> Option<String> { | ||||
|         use rand::rngs::OsRng; | ||||
|         use rand::RngCore; | ||||
|         use rand::{rngs::OsRng, RngCore}; | ||||
|         let len = self.nodes.len(); | ||||
|         if len == 0 { | ||||
|             return None; | ||||
| @ -367,13 +313,12 @@ impl Store { | ||||
| 
 | ||||
| #[cfg(test)] | ||||
| mod tests { | ||||
|     use super::*; | ||||
|     use dashmap::DashMap; | ||||
|     use ed25519_dalek::SigningKey; | ||||
|     use rand::rngs::OsRng; | ||||
|     use std::time::{SystemTime, UNIX_EPOCH}; | ||||
|     use tokio::fs::File; | ||||
|     use tokio::io::AsyncWriteExt; | ||||
| 
 | ||||
|     use dashmap::DashMap; | ||||
|     use tokio::{fs::File, io::AsyncWriteExt}; | ||||
| 
 | ||||
|     use super::*; | ||||
| 
 | ||||
|     async fn setup_file_manager(function: &str) -> std::io::Result<FileManager> { | ||||
|         let _ = tokio::fs::create_dir_all(".tmp").await; | ||||
| @ -386,14 +331,11 @@ mod tests { | ||||
| 
 | ||||
|     #[test] | ||||
|     fn node_info_creation() { | ||||
|         let keypair = SigningKey::generate(&mut OsRng); | ||||
|         let node_info = NodeInfo { | ||||
|             pubkey: keypair.verifying_key(), | ||||
|             updated_at: SystemTime::now(), | ||||
|             public: true, | ||||
|         }; | ||||
|         let keypair = Keypair::new(); | ||||
|         let node_info = | ||||
|             NodeInfo { pubkey: keypair.pubkey(), updated_at: SystemTime::now(), public: true }; | ||||
| 
 | ||||
|         assert_eq!(node_info.pubkey, keypair.verifying_key()); | ||||
|         assert_eq!(node_info.pubkey, keypair.pubkey()); | ||||
|         assert!(node_info.updated_at >= UNIX_EPOCH); | ||||
|         assert!(node_info.public); | ||||
|     } | ||||
| @ -426,18 +368,18 @@ mod tests { | ||||
| 
 | ||||
|     #[tokio::test] | ||||
|     async fn sign_message_with_key() { | ||||
|         let keypair = SigningKey::generate(&mut OsRng); | ||||
|         let pubkey_hex = hex::encode(keypair.verifying_key().as_bytes()); | ||||
|         let keypair = Keypair::new(); | ||||
|         let pubkey_string = keypair.pubkey().to_string(); | ||||
|         let store = Store { | ||||
|             nodes: DashMap::new(), | ||||
|             conns: DashSet::new(), | ||||
|             keys: DashMap::new(), | ||||
|             persistence: setup_file_manager("sign_message_with_key").await.unwrap(), | ||||
|         }; | ||||
|         store.keys.insert(keypair.verifying_key(), keypair); | ||||
|         store.keys.insert(keypair.pubkey(), keypair); | ||||
| 
 | ||||
|         let message = "Test message"; | ||||
|         let result = store.sign_message_with_key(message, &pubkey_hex).await; | ||||
|         let result = store.sign_message_with_key(message, &pubkey_string).await; | ||||
| 
 | ||||
|         assert!(result.is_ok()); | ||||
|         if let Ok(signature) = result { | ||||
| @ -447,15 +389,12 @@ mod tests { | ||||
| 
 | ||||
|     #[tokio::test] | ||||
|     async fn process_node_update() { | ||||
|         let keypair = SigningKey::generate(&mut OsRng); | ||||
|         let keypair = Keypair::new(); | ||||
|         let node_update = NodeUpdate { | ||||
|             ip: "127.0.0.1".to_string(), | ||||
|             keypair: hex::encode(keypair.as_bytes()), | ||||
|             keypair: keypair.to_base58_string(), | ||||
|             updated_at: Some(prost_types::Timestamp { | ||||
|                 seconds: SystemTime::now() | ||||
|                     .duration_since(UNIX_EPOCH) | ||||
|                     .unwrap() | ||||
|                     .as_secs() as i64, | ||||
|                 seconds: SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs() as i64, | ||||
|                 nanos: 0, | ||||
|             }), | ||||
|             public: true, | ||||
| @ -476,12 +415,9 @@ mod tests { | ||||
| 
 | ||||
|     #[tokio::test] | ||||
|     async fn get_full_node_list() { | ||||
|         let keypair = SigningKey::generate(&mut OsRng); | ||||
|         let node_info = NodeInfo { | ||||
|             pubkey: keypair.verifying_key(), | ||||
|             updated_at: SystemTime::now(), | ||||
|             public: true, | ||||
|         }; | ||||
|         let keypair = Keypair::new(); | ||||
|         let node_info = | ||||
|             NodeInfo { pubkey: keypair.pubkey(), updated_at: SystemTime::now(), public: true }; | ||||
| 
 | ||||
|         let store = Store { | ||||
|             nodes: DashMap::new(), | ||||
| @ -491,13 +427,13 @@ mod tests { | ||||
|         }; | ||||
| 
 | ||||
|         store.nodes.insert("127.0.0.1".to_string(), node_info); | ||||
|         store.keys.insert(keypair.verifying_key(), keypair.clone()); | ||||
|         store.keys.insert(keypair.pubkey(), Keypair::from_bytes(&keypair.to_bytes()).unwrap()); | ||||
| 
 | ||||
|         let node_list = store.get_full_node_list().await; | ||||
| 
 | ||||
|         assert_eq!(node_list.len(), 1); | ||||
|         assert_eq!(node_list[0].ip, "127.0.0.1"); | ||||
|         assert_eq!(node_list[0].keypair, hex::encode(keypair.as_bytes())); | ||||
|         assert_eq!(node_list[0].keypair, keypair.to_base58_string()); | ||||
|         assert!(node_list[0].public); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -1,9 +1,11 @@ | ||||
| use crate::datastore::{SigningError, Store}; | ||||
| use actix_web::error::ResponseError; | ||||
| use actix_web::http::StatusCode; | ||||
| use actix_web::{web, App, HttpResponse, HttpServer, Responder, Result}; | ||||
| use serde::Deserialize; | ||||
| use std::fmt; | ||||
| use std::sync::Arc; | ||||
| 
 | ||||
| use salvo::affix; | ||||
| use salvo::prelude::*; | ||||
| 
 | ||||
| const HOMEPAGE: &str = r#"Welcome, beloved hacker!
 | ||||
| 
 | ||||
| I am a node that is part of the DeTEE Hacker Challenge network. | ||||
| @ -33,6 +35,7 @@ If you manage to steal a key, contact us at https://detee.cloud | ||||
| Good luck! | ||||
| "#;
 | ||||
| 
 | ||||
| #[derive(Debug)] | ||||
| enum HTTPError { | ||||
|     NoKeyID, | ||||
|     NoPubkey, | ||||
| @ -40,42 +43,53 @@ enum HTTPError { | ||||
|     Store(SigningError), | ||||
| } | ||||
| 
 | ||||
| #[async_trait] | ||||
| impl Writer for HTTPError { | ||||
|     async fn write(self, _req: &mut Request, _depot: &mut Depot, res: &mut Response) { | ||||
|         res.status_code(StatusCode::BAD_REQUEST); | ||||
|         match self { | ||||
|             HTTPError::NoKeyID => res.render("key ID must be specified as a get param"), | ||||
|             HTTPError::NoPubkey => res.render("pubkey must be specified as GET param"), | ||||
|             HTTPError::NoMessage => res.render("something must be specified as GET param"), | ||||
|             HTTPError::Store(e) => res.render(format!("{e}")), | ||||
|         }; | ||||
| impl fmt::Display for HTTPError { | ||||
|     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||||
|         match *self { | ||||
|             HTTPError::NoKeyID => write!(f, "Key ID must be specified as a query param"), | ||||
|             HTTPError::NoPubkey => write!(f, "Pubkey must be specified as a query param"), | ||||
|             HTTPError::NoMessage => write!(f, "Something must be specified as a query param"), | ||||
|             HTTPError::Store(ref err) => write!(f, "{}", err), | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[handler] | ||||
| async fn homepage() -> String { | ||||
|     HOMEPAGE.to_string() | ||||
| impl ResponseError for HTTPError { | ||||
|     fn status_code(&self) -> StatusCode { | ||||
|         StatusCode::BAD_REQUEST | ||||
|     } | ||||
| 
 | ||||
|     fn error_response(&self) -> HttpResponse { | ||||
|         HttpResponse::BadRequest().body(self.to_string()) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[handler] | ||||
| async fn memory_list(depot: &mut Depot) -> String { | ||||
|     let ds = depot.obtain::<Arc<Store>>().unwrap(); | ||||
|     ds.tabled_memory_list().await // TODO: make this paginated
 | ||||
| #[derive(Deserialize)] | ||||
| struct SignQuery { | ||||
|     pubkey: Option<String>, | ||||
|     something: Option<String>, | ||||
|     key: Option<u64>, | ||||
|     page: Option<u64>, | ||||
| } | ||||
| 
 | ||||
| #[handler] | ||||
| async fn memory_sign(req: &mut Request, depot: &mut Depot) -> Result<String, HTTPError> { | ||||
|     let ds = depot.obtain::<Arc<Store>>().unwrap(); | ||||
|     let pubkey = match req.query::<String>("pubkey") { | ||||
|         Some(k) => k, | ||||
|         None => return Err(HTTPError::NoPubkey), | ||||
|     }; | ||||
| async fn homepage() -> impl Responder { | ||||
|     HttpResponse::Ok().body(HOMEPAGE) | ||||
| } | ||||
| 
 | ||||
|     let something = match req.query::<String>("something") { | ||||
|         Some(k) => k, | ||||
|         None => return Err(HTTPError::NoMessage), | ||||
|     }; | ||||
| async fn memory_list(store: web::Data<Arc<Store>>) -> impl Responder { | ||||
|     let ds = store.get_ref(); | ||||
|     let list = ds.tabled_memory_list().await; // TODO: make paginated
 | ||||
|     HttpResponse::Ok().body(list) | ||||
| } | ||||
| 
 | ||||
| async fn memory_sign( | ||||
|     store: web::Data<Arc<Store>>, | ||||
|     req: web::Query<SignQuery>, | ||||
| ) -> Result<String, HTTPError> { | ||||
|     let ds = store.get_ref(); | ||||
| 
 | ||||
|     let pubkey = req.pubkey.clone().ok_or(HTTPError::NoPubkey)?; | ||||
|     let something = req.something.clone().ok_or(HTTPError::NoMessage)?; | ||||
| 
 | ||||
|     match ds.sign_message_with_key(&something, &pubkey).await { | ||||
|         Ok(s) => Ok(s), | ||||
| @ -83,29 +97,21 @@ async fn memory_sign(req: &mut Request, depot: &mut Depot) -> Result<String, HTT | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[handler] | ||||
| async fn disk_list(req: &mut Request, depot: &mut Depot) -> Result<String, HTTPError> { | ||||
|     let ds = depot.obtain::<Arc<Store>>().unwrap(); | ||||
|     let page = match req.query::<u64>("page") { | ||||
|         Some(n) => n, | ||||
|         None => 0, | ||||
|     }; | ||||
| 
 | ||||
|     Ok(ds.tabled_disk_list(page).await) | ||||
| async fn disk_list(store: web::Data<Arc<Store>>, req: web::Query<SignQuery>) -> impl Responder { | ||||
|     let ds = store.get_ref(); | ||||
|     let page = req.page.unwrap_or(0); | ||||
|     let list = ds.tabled_disk_list(page).await; | ||||
|     HttpResponse::Ok().body(list) | ||||
| } | ||||
| 
 | ||||
| #[handler] | ||||
| async fn disk_sign(req: &mut Request, depot: &mut Depot) -> Result<String, HTTPError> { | ||||
|     let ds = depot.obtain::<Arc<Store>>().unwrap(); | ||||
|     let key = match req.query::<u64>("key") { | ||||
|         Some(k) => k, | ||||
|         None => return Err(HTTPError::NoKeyID), | ||||
|     }; | ||||
| async fn disk_sign( | ||||
|     store: web::Data<Arc<Store>>, | ||||
|     req: web::Query<SignQuery>, | ||||
| ) -> Result<String, HTTPError> { | ||||
|     let ds = store.get_ref(); | ||||
| 
 | ||||
|     let something = match req.query::<String>("something") { | ||||
|         Some(k) => k, | ||||
|         None => return Err(HTTPError::NoMessage), | ||||
|     }; | ||||
|     let key = req.key.ok_or(HTTPError::NoKeyID)?; | ||||
|     let something = req.something.clone().ok_or(HTTPError::NoMessage)?; | ||||
| 
 | ||||
|     match ds.disk_sign_message_with_key(&something, key).await { | ||||
|         Ok(s) => Ok(s), | ||||
| @ -114,20 +120,24 @@ async fn disk_sign(req: &mut Request, depot: &mut Depot) -> Result<String, HTTPE | ||||
| } | ||||
| 
 | ||||
| pub async fn init(ds: Arc<Store>) { | ||||
|     let acceptor = TcpListener::new("0.0.0.0:31372").bind().await; | ||||
|     let router = Router::new() | ||||
|         .hoop(affix::inject(ds)) | ||||
|         .get(homepage) | ||||
|         .push( | ||||
|             Router::with_path("memory") | ||||
|                 .get(memory_list) | ||||
|                 .push(Router::with_path("sign").get(memory_sign)), | ||||
|         ) | ||||
|         .push( | ||||
|             Router::with_path("disk") | ||||
|                 .get(disk_list) | ||||
|                 .push(Router::with_path("sign").get(disk_sign)), | ||||
|         ); | ||||
|     println!("{:?}", router); | ||||
|     Server::new(acceptor).serve(router).await; | ||||
|     HttpServer::new(move || { | ||||
|         App::new() | ||||
|             .app_data(web::Data::new(ds.clone())) | ||||
|             .route("/", web::get().to(homepage)) | ||||
|             .service( | ||||
|                 web::scope("/memory") | ||||
|                     .route("", web::get().to(memory_list)) | ||||
|                     .route("/sign", web::get().to(memory_sign)), | ||||
|             ) | ||||
|             .service( | ||||
|                 web::scope("/disk") | ||||
|                     .route("", web::get().to(disk_list)) | ||||
|                     .route("/sign", web::get().to(disk_sign)), | ||||
|             ) | ||||
|     }) | ||||
|     .bind("0.0.0.0:31372") | ||||
|     .unwrap() | ||||
|     .run() | ||||
|     .await | ||||
|     .unwrap(); | ||||
| } | ||||
|  | ||||
| @ -1,14 +1,17 @@ | ||||
| #![allow(dead_code)] | ||||
| use ed25519_dalek::SigningKey; | ||||
| use ed25519_dalek::KEYPAIR_LENGTH; | ||||
| use std::net::AddrParseError; | ||||
| use std::net::Ipv4Addr; | ||||
| use std::time::{Duration, SystemTime, UNIX_EPOCH}; | ||||
| use tokio::fs::File; | ||||
| use tokio::io::{AsyncReadExt, AsyncSeekExt, AsyncWriteExt, SeekFrom}; | ||||
| use tokio::sync::Mutex; | ||||
| use solana_sdk::signer::keypair::Keypair; | ||||
| use std::{ | ||||
|     net::{AddrParseError, Ipv4Addr}, | ||||
|     time::{Duration, SystemTime, UNIX_EPOCH}, | ||||
| }; | ||||
| use tokio::{ | ||||
|     fs::File, | ||||
|     io::{AsyncReadExt, AsyncSeekExt, AsyncWriteExt, SeekFrom}, | ||||
|     sync::Mutex, | ||||
| }; | ||||
| 
 | ||||
| const DATA_SIZE: usize = 76; | ||||
| const KEYPAIR_LENGTH: usize = 64; | ||||
| 
 | ||||
| pub enum Error { | ||||
|     CorruptedIP, | ||||
| @ -20,21 +23,25 @@ impl From<AddrParseError> for Error { | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[derive(Clone)] | ||||
| pub struct Node { | ||||
|     pub ip: Ipv4Addr, | ||||
|     pub keypair: SigningKey, | ||||
|     pub keypair: Keypair, | ||||
|     pub joined_at: SystemTime, | ||||
| } | ||||
| 
 | ||||
| impl TryFrom<(&str, SigningKey, SystemTime)> for Node { | ||||
| impl Clone for Node { | ||||
|     fn clone(&self) -> Self { | ||||
|         let cloned_keypair = Keypair::from_bytes(&self.keypair.to_bytes()).unwrap(); | ||||
| 
 | ||||
|         Node { ip: self.ip, keypair: cloned_keypair, joined_at: self.joined_at } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl TryFrom<(&str, Keypair, SystemTime)> for Node { | ||||
|     type Error = Error; | ||||
|     fn try_from(value: (&str, SigningKey, SystemTime)) -> Result<Self, Self::Error> { | ||||
|         Ok(Self { | ||||
|             ip: value.0.parse()?, | ||||
|             keypair: value.1, | ||||
|             joined_at: value.2, | ||||
|         }) | ||||
| 
 | ||||
|     fn try_from(value: (&str, Keypair, SystemTime)) -> Result<Self, Self::Error> { | ||||
|         Ok(Self { ip: value.0.parse()?, keypair: value.1, joined_at: value.2 }) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| @ -43,21 +50,16 @@ impl Node { | ||||
|         self.ip.to_string() | ||||
|     } | ||||
| 
 | ||||
|     fn signing_key(&self) -> SigningKey { | ||||
|         self.keypair.clone() | ||||
|     fn signing_key(&self) -> Keypair { | ||||
|         Keypair::from_bytes(&self.keypair.to_bytes()).unwrap() | ||||
|     } | ||||
| 
 | ||||
|     fn to_bytes(self) -> [u8; DATA_SIZE] { | ||||
|         let mut result = [0; DATA_SIZE]; | ||||
|         result[0..4].copy_from_slice(&self.ip.octets()); | ||||
|         result[4..68].copy_from_slice(&self.keypair.to_keypair_bytes()); | ||||
|         result[4..68].copy_from_slice(&self.keypair.to_bytes()); | ||||
|         result[68..DATA_SIZE].copy_from_slice( | ||||
|             &self | ||||
|                 .joined_at | ||||
|                 .duration_since(UNIX_EPOCH) | ||||
|                 .unwrap() | ||||
|                 .as_secs() | ||||
|                 .to_le_bytes(), | ||||
|             &self.joined_at.duration_since(UNIX_EPOCH).unwrap().as_secs().to_le_bytes(), | ||||
|         ); | ||||
|         result | ||||
|     } | ||||
| @ -66,15 +68,11 @@ impl Node { | ||||
|         let ip: [u8; 4] = bytes[0..4].try_into().unwrap(); | ||||
|         let ip: Ipv4Addr = ip.into(); | ||||
|         let keypair: [u8; KEYPAIR_LENGTH] = bytes[4..68].try_into().unwrap(); | ||||
|         let keypair: SigningKey = SigningKey::from_keypair_bytes(&keypair).unwrap(); | ||||
|         let keypair: Keypair = Keypair::from_bytes(&keypair).unwrap(); | ||||
|         let joined_at: [u8; 8] = bytes[68..DATA_SIZE].try_into().unwrap(); | ||||
|         let joined_at: u64 = u64::from_le_bytes(joined_at); | ||||
|         let joined_at = SystemTime::UNIX_EPOCH + Duration::from_secs(joined_at); | ||||
|         Self { | ||||
|             ip, | ||||
|             keypair, | ||||
|             joined_at, | ||||
|         } | ||||
|         Self { ip, keypair, joined_at } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| @ -85,9 +83,7 @@ pub struct FileManager { | ||||
| impl FileManager { | ||||
|     pub async fn init(path: &str) -> std::io::Result<Self> { | ||||
|         let file = File::options().read(true).append(true).open(path).await?; | ||||
|         Ok(Self { | ||||
|             file: Mutex::new(file), | ||||
|         }) | ||||
|         Ok(Self { file: Mutex::new(file) }) | ||||
|     } | ||||
| 
 | ||||
|     pub async fn append_node(&self, node: Node) -> std::io::Result<()> { | ||||
| @ -100,10 +96,7 @@ impl FileManager { | ||||
| 
 | ||||
|     pub async fn get_node_by_id(&self, id: u64) -> std::io::Result<Node> { | ||||
|         let mut file = self.file.lock().await; | ||||
|         file.seek(SeekFrom::Start( | ||||
|             id.wrapping_mul(DATA_SIZE.try_into().unwrap_or(0)), | ||||
|         )) | ||||
|         .await?; | ||||
|         file.seek(SeekFrom::Start(id.wrapping_mul(DATA_SIZE.try_into().unwrap_or(0)))).await?; | ||||
|         let mut node_bytes = [0; DATA_SIZE]; | ||||
|         file.read_exact(&mut node_bytes).await?; | ||||
|         Ok(Node::from_bytes(node_bytes)) | ||||
| @ -113,11 +106,7 @@ impl FileManager { | ||||
|     /// Specify offset (the number of nodes to skip).
 | ||||
|     pub async fn get_page_of_20(&self, offset: u64) -> std::io::Result<Vec<Node>> { | ||||
|         let mut file = self.file.lock().await; | ||||
|         file.seek(SeekFrom::Start( | ||||
|             offset | ||||
|                 .wrapping_mul(DATA_SIZE.try_into().unwrap_or(0)), | ||||
|         )) | ||||
|         .await?; | ||||
|         file.seek(SeekFrom::Start(offset.wrapping_mul(DATA_SIZE.try_into().unwrap_or(0)))).await?; | ||||
|         let mut nodes = Vec::new(); | ||||
|         let mut count = 0; | ||||
|         loop { | ||||
| @ -139,12 +128,9 @@ impl FileManager { | ||||
| mod tests { | ||||
| 
 | ||||
|     use super::*; | ||||
|     use ed25519_dalek::SigningKey; | ||||
|     use rand::rngs::OsRng; | ||||
|     use rand::Rng; | ||||
|     use std::io::Result; | ||||
|     use tokio::fs::remove_file; | ||||
|     use tokio::io::AsyncWriteExt; | ||||
|     use tokio::{fs::remove_file, io::AsyncWriteExt}; | ||||
| 
 | ||||
|     const TEST_FILE_PREFIX: &str = ".tmp/test_"; | ||||
|     fn get_test_file_name(function: &str) -> String { | ||||
| @ -162,11 +148,11 @@ mod tests { | ||||
| 
 | ||||
|     #[test] | ||||
|     fn node_round_trip() { | ||||
|         let keypair = SigningKey::generate(&mut OsRng); | ||||
|         let keypair = Keypair::new(); | ||||
| 
 | ||||
|         let original_node = Node { | ||||
|             ip: "192.168.1.1".parse().unwrap(), | ||||
|             keypair: keypair.clone(), | ||||
|             keypair: Keypair::from_bytes(&keypair.to_bytes()).unwrap(), | ||||
|             joined_at: SystemTime::now(), | ||||
|         }; | ||||
| 
 | ||||
| @ -174,21 +160,10 @@ mod tests { | ||||
|         let restored_node = Node::from_bytes(node_bytes); | ||||
| 
 | ||||
|         assert_eq!(original_node.ip_as_string(), restored_node.ip_as_string()); | ||||
|         assert_eq!(original_node.keypair.to_bytes(), restored_node.keypair.to_bytes()); | ||||
|         assert_eq!( | ||||
|             original_node.keypair.to_keypair_bytes(), | ||||
|             restored_node.keypair.to_keypair_bytes() | ||||
|         ); | ||||
|         assert_eq!( | ||||
|             original_node | ||||
|                 .joined_at | ||||
|                 .duration_since(UNIX_EPOCH) | ||||
|                 .unwrap() | ||||
|                 .as_secs(), | ||||
|             restored_node | ||||
|                 .joined_at | ||||
|                 .duration_since(UNIX_EPOCH) | ||||
|                 .unwrap() | ||||
|                 .as_secs() | ||||
|             original_node.joined_at.duration_since(UNIX_EPOCH).unwrap().as_secs(), | ||||
|             restored_node.joined_at.duration_since(UNIX_EPOCH).unwrap().as_secs() | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
| @ -204,12 +179,12 @@ mod tests { | ||||
|     } | ||||
| 
 | ||||
|     fn get_random_node() -> Node { | ||||
|         let keypair = SigningKey::generate(&mut OsRng); | ||||
|         let keypair = Keypair::new(); | ||||
|         let mut rng = rand::thread_rng(); | ||||
|         let ipv4 = Ipv4Addr::new(rng.gen(), rng.gen(), rng.gen(), rng.gen()); | ||||
|         Node { | ||||
|             ip: ipv4, | ||||
|             keypair: keypair.clone(), | ||||
|             keypair: Keypair::from_bytes(&keypair.to_bytes()).unwrap(), | ||||
|             joined_at: SystemTime::now(), | ||||
|         } | ||||
|     } | ||||
| @ -225,11 +200,7 @@ mod tests { | ||||
|         assert_eq!(node.keypair, retrieved_node.keypair); | ||||
|         assert_eq!( | ||||
|             node.joined_at.duration_since(UNIX_EPOCH).unwrap().as_secs(), | ||||
|             retrieved_node | ||||
|                 .joined_at | ||||
|                 .duration_since(UNIX_EPOCH) | ||||
|                 .unwrap() | ||||
|                 .as_secs() | ||||
|             retrieved_node.joined_at.duration_since(UNIX_EPOCH).unwrap().as_secs() | ||||
|         ); | ||||
|         remove_file(get_test_file_name(function_name)).await?; | ||||
|         Ok(()) | ||||
| @ -248,10 +219,7 @@ mod tests { | ||||
|         manager.append_node(node3.clone()).await.unwrap(); | ||||
|         let retrieved_node2 = manager.get_node_by_id(1).await?; | ||||
|         assert_eq!(node1.ip_as_string(), retrieved_node1.ip_as_string()); | ||||
|         assert_eq!( | ||||
|             node1.keypair.to_keypair_bytes(), | ||||
|             retrieved_node1.keypair.to_keypair_bytes() | ||||
|         ); | ||||
|         assert_eq!(node1.keypair.to_bytes(), retrieved_node1.keypair.to_bytes()); | ||||
|         assert_eq!(node2.ip_as_string(), retrieved_node2.ip_as_string()); | ||||
|         assert_eq!(node2.keypair, retrieved_node2.keypair); | ||||
|         let retrieved_node3 = manager.get_node_by_id(2).await?; | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user