notedeck

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

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 }