notedeck

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

key_parsing.rs (7505B)


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