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