notedeck

One damus client to rule them all
git clone git://jb55.com/notedeck
Log | Files | Refs | README | LICENSE

key_parsing.rs (7601B)


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