key_parsing.rs (7499B)
1 use std::collections::HashMap; 2 use std::str::FromStr; 3 4 use crate::Error; 5 use enostr::{Keypair, Pubkey, SecretKey}; 6 use poll_promise::Promise; 7 use reqwest::{Request, Response}; 8 use serde::{Deserialize, Serialize}; 9 10 #[derive(Debug, PartialEq)] 11 pub enum LoginError { 12 InvalidKey, 13 Nip05Failed(String), 14 } 15 16 impl std::fmt::Display for LoginError { 17 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 18 match self { 19 LoginError::InvalidKey => write!(f, "The inputted key is invalid."), 20 LoginError::Nip05Failed(e) => write!(f, "Failed to get pubkey from Nip05 address: {e}"), 21 } 22 } 23 } 24 25 impl std::error::Error for LoginError {} 26 27 #[derive(Deserialize, Serialize)] 28 pub struct Nip05Result { 29 pub names: HashMap<String, String>, 30 pub relays: Option<HashMap<String, Vec<String>>>, 31 } 32 33 async fn parse_nip05_response(response: Response) -> Result<Nip05Result, Error> { 34 match response.bytes().await { 35 Ok(bytes) => { 36 serde_json::from_slice::<Nip05Result>(&bytes).map_err(|e| Error::Generic(e.to_string())) 37 } 38 Err(e) => Err(Error::Generic(e.to_string())), 39 } 40 } 41 42 fn get_pubkey_from_result(result: Nip05Result, user: String) -> Result<Pubkey, Error> { 43 match result.names.get(&user).to_owned() { 44 Some(pubkey_str) => Pubkey::from_hex(pubkey_str).map_err(|e| { 45 Error::Generic("Could not parse pubkey: ".to_string() + e.to_string().as_str()) 46 }), 47 None => Err(Error::Generic("Could not find user in json.".to_string())), 48 } 49 } 50 51 async fn get_nip05_pubkey(id: &str) -> Result<Pubkey, Error> { 52 let mut parts = id.split('@'); 53 54 let user = match parts.next() { 55 Some(user) => user, 56 None => { 57 return Err(Error::Generic( 58 "Address does not contain username.".to_string(), 59 )); 60 } 61 }; 62 let host = match parts.next() { 63 Some(host) => host, 64 None => { 65 return Err(Error::Generic( 66 "Nip05 address does not contain host.".to_string(), 67 )); 68 } 69 }; 70 71 if parts.next().is_some() { 72 return Err(Error::Generic( 73 "Nip05 address contains extraneous parts.".to_string(), 74 )); 75 } 76 77 let url = format!("https://{host}/.well-known/nostr.json?name={user}"); 78 let request = Request::new(reqwest::Method::GET, url.parse().unwrap()); 79 let cloned_user = user.to_string(); 80 81 let client = reqwest::Client::new(); 82 match client.execute(request).await { 83 Ok(resp) => match parse_nip05_response(resp).await { 84 Ok(result) => match get_pubkey_from_result(result, cloned_user) { 85 Ok(pubkey) => Ok(pubkey), 86 Err(e) => Err(Error::Generic(e.to_string())), 87 }, 88 Err(e) => Err(Error::Generic(e.to_string())), 89 }, 90 Err(e) => Err(Error::Generic(e.to_string())), 91 } 92 } 93 94 fn retrieving_nip05_pubkey(key: &str) -> bool { 95 key.contains('@') 96 } 97 98 pub fn perform_key_retrieval(key: &str) -> Promise<Result<Keypair, LoginError>> { 99 let key_string = String::from(key); 100 Promise::spawn_async(async move { get_login_key(&key_string).await }) 101 } 102 103 /// Attempts to turn a string slice key from the user into a Nostr-Sdk Keypair object. 104 /// The `key` can be in any of the following formats: 105 /// - Public Bech32 key (prefix "npub"): "npub1xyz..." 106 /// - Private Bech32 key (prefix "nsec"): "nsec1xyz..." 107 /// - Public hex key: "02a1..." 108 /// - Private hex key: "5dab..." 109 /// - NIP-05 address: "example@nostr.com" 110 /// 111 pub async fn get_login_key(key: &str) -> Result<Keypair, LoginError> { 112 let tmp_key: &str = if let Some(stripped) = key.strip_prefix('@') { 113 stripped 114 } else { 115 key 116 }; 117 118 if retrieving_nip05_pubkey(tmp_key) { 119 match get_nip05_pubkey(tmp_key).await { 120 Ok(pubkey) => Ok(Keypair::only_pubkey(pubkey)), 121 Err(e) => Err(LoginError::Nip05Failed(e.to_string())), 122 } 123 } else if let Ok(pubkey) = Pubkey::try_from_bech32_string(tmp_key, true) { 124 Ok(Keypair::only_pubkey(pubkey)) 125 } else if let Ok(pubkey) = Pubkey::try_from_hex_str_with_verify(tmp_key) { 126 Ok(Keypair::only_pubkey(pubkey)) 127 } else if let Ok(secret_key) = SecretKey::from_str(tmp_key) { 128 Ok(Keypair::from_secret(secret_key)) 129 } else { 130 Err(LoginError::InvalidKey) 131 } 132 } 133 134 #[cfg(test)] 135 mod tests { 136 use super::*; 137 use crate::promise_assert; 138 139 #[tokio::test] 140 async fn test_pubkey_async() { 141 let pubkey_str = "npub1xtscya34g58tk0z605fvr788k263gsu6cy9x0mhnm87echrgufzsevkk5s"; 142 let expected_pubkey = 143 Pubkey::try_from_bech32_string(pubkey_str, false).expect("Should not have errored."); 144 let login_key_result = get_login_key(pubkey_str).await; 145 146 assert_eq!(Ok(Keypair::only_pubkey(expected_pubkey)), login_key_result); 147 } 148 149 #[tokio::test(flavor = "multi_thread", worker_threads = 2)] 150 async fn test_pubkey() { 151 let pubkey_str = "npub1xtscya34g58tk0z605fvr788k263gsu6cy9x0mhnm87echrgufzsevkk5s"; 152 let expected_pubkey = 153 Pubkey::try_from_bech32_string(pubkey_str, false).expect("Should not have errored."); 154 let login_key_result = perform_key_retrieval(pubkey_str); 155 156 promise_assert!( 157 assert_eq, 158 Ok(Keypair::only_pubkey(expected_pubkey)), 159 &login_key_result 160 ); 161 } 162 163 #[tokio::test(flavor = "multi_thread", worker_threads = 2)] 164 async fn test_hex_pubkey() { 165 let pubkey_str = "32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245"; 166 let expected_pubkey = Pubkey::from_hex(pubkey_str).expect("Should not have errored."); 167 let login_key_result = perform_key_retrieval(pubkey_str); 168 169 promise_assert!( 170 assert_eq, 171 Ok(Keypair::only_pubkey(expected_pubkey)), 172 &login_key_result 173 ); 174 } 175 176 #[tokio::test(flavor = "multi_thread", worker_threads = 2)] 177 async fn test_privkey() { 178 let privkey_str = "nsec1g8wt3hlwjpa4827xylr3r0lccufxltyekhraexes8lqmpp2hensq5aujhs"; 179 let expected_privkey = SecretKey::from_str(privkey_str).expect("Should not have errored."); 180 let login_key_result = perform_key_retrieval(privkey_str); 181 182 promise_assert!( 183 assert_eq, 184 Ok(Keypair::from_secret(expected_privkey)), 185 &login_key_result 186 ); 187 } 188 189 #[tokio::test(flavor = "multi_thread", worker_threads = 2)] 190 async fn test_hex_privkey() { 191 let privkey_str = "41dcb8dfee907b53abc627c711bff8c7126fac99b5c7dc9b303fc1b08557cce0"; 192 let expected_privkey = SecretKey::from_str(privkey_str).expect("Should not have errored."); 193 let login_key_result = perform_key_retrieval(privkey_str); 194 195 promise_assert!( 196 assert_eq, 197 Ok(Keypair::from_secret(expected_privkey)), 198 &login_key_result 199 ); 200 } 201 202 #[tokio::test(flavor = "multi_thread", worker_threads = 2)] 203 async fn test_nip05() { 204 let nip05_str = "damus@damus.io"; 205 let expected_pubkey = Pubkey::try_from_bech32_string( 206 "npub18m76awca3y37hkvuneavuw6pjj4525fw90necxmadrvjg0sdy6qsngq955", 207 false, 208 ) 209 .expect("Should not have errored."); 210 let login_key_result = perform_key_retrieval(nip05_str); 211 212 promise_assert!( 213 assert_eq, 214 Ok(Keypair::only_pubkey(expected_pubkey)), 215 &login_key_result 216 ); 217 } 218 }