field_lib

Cross-platform Rust runtime for Radroots iOS and Android apps
git clone https://radroots.dev/git/field_lib.git
Log | Files | Refs | README | LICENSE

key_management.rs (12891B)


      1 use super::RadrootsRuntime;
      2 use crate::RadrootsAppError;
      3 #[cfg(feature = "nostr-client")]
      4 use radroots_identity::{RadrootsIdentity, RadrootsIdentityId};
      5 
      6 #[derive(uniffi::Record, Debug, Clone)]
      7 pub struct NostrIdentityRecord {
      8     pub id: String,
      9     pub public_key_hex: String,
     10     pub public_key_npub: String,
     11     pub label: Option<String>,
     12     pub is_selected: bool,
     13 }
     14 
     15 #[derive(uniffi::Record, Debug, Clone)]
     16 pub struct NostrIdentitySnapshot {
     17     pub has_selected_signing_identity: bool,
     18     pub selected_identity_id: Option<String>,
     19     pub selected_npub: Option<String>,
     20     pub identities: Vec<NostrIdentityRecord>,
     21 }
     22 
     23 #[derive(uniffi::Record, Debug, Clone)]
     24 pub struct NostrHostCustodyIdentity {
     25     pub id: String,
     26     pub public_key_hex: String,
     27     pub public_key_npub: String,
     28 }
     29 
     30 #[cfg(feature = "nostr-client")]
     31 fn account_record(
     32     net: &radroots_net_core::Net,
     33     account_id: &RadrootsIdentityId,
     34 ) -> Result<NostrIdentityRecord, RadrootsAppError> {
     35     let selected_identity_id = net
     36         .accounts
     37         .default_account_id()
     38         .map_err(|e| RadrootsAppError::identity(format!("{e}")))?;
     39     let account = net
     40         .accounts
     41         .list_accounts()
     42         .map_err(|e| RadrootsAppError::identity(format!("{e}")))?
     43         .into_iter()
     44         .find(|account| &account.account_id == account_id)
     45         .ok_or_else(|| RadrootsAppError::identity(format!("identity not found: {account_id}")))?;
     46     let is_selected = selected_identity_id
     47         .as_ref()
     48         .map(|selected| selected == &account.account_id)
     49         .unwrap_or(false);
     50 
     51     Ok(NostrIdentityRecord {
     52         id: account.account_id.to_string(),
     53         public_key_hex: account.public_identity.public_key_hex,
     54         public_key_npub: account.public_identity.public_key_npub,
     55         label: account.label,
     56         is_selected,
     57     })
     58 }
     59 
     60 #[cfg(feature = "nostr-client")]
     61 fn invalidate_nostr_runtime(net: &mut radroots_net_core::Net) {
     62     net.set_nostr_signer(None);
     63     net.nostr = None;
     64 }
     65 
     66 #[cfg(feature = "nostr-client")]
     67 fn identity_from_secret(secret_key: &str) -> Result<RadrootsIdentity, RadrootsAppError> {
     68     RadrootsIdentity::from_secret_key_str(secret_key)
     69         .map_err(|e| RadrootsAppError::identity(format!("{e}")))
     70 }
     71 
     72 #[cfg(feature = "nostr-client")]
     73 fn host_custody_identity_from_secret(
     74     secret_key: &str,
     75 ) -> Result<(RadrootsIdentity, NostrHostCustodyIdentity), RadrootsAppError> {
     76     let identity = identity_from_secret(secret_key)?;
     77     let record = NostrHostCustodyIdentity {
     78         id: identity.id().to_string(),
     79         public_key_hex: identity.public_key_hex(),
     80         public_key_npub: identity.public_key_npub(),
     81     };
     82     Ok((identity, record))
     83 }
     84 
     85 #[cfg(feature = "nostr-client")]
     86 fn restore_host_custody_identity(
     87     net: &mut radroots_net_core::Net,
     88     identity: &RadrootsIdentity,
     89     label: Option<String>,
     90     make_selected: bool,
     91 ) -> Result<NostrIdentityRecord, RadrootsAppError> {
     92     let account_id = net
     93         .accounts
     94         .upsert_identity(identity, label, make_selected)
     95         .map_err(|e| RadrootsAppError::identity(format!("{e}")))?;
     96     invalidate_nostr_runtime(net);
     97     account_record(net, &account_id)
     98 }
     99 
    100 #[cfg_attr(not(coverage_nightly), uniffi::export)]
    101 impl RadrootsRuntime {
    102     pub fn nostr_identity_has_selected_signing_identity(&self) -> bool {
    103         #[cfg(feature = "nostr-client")]
    104         {
    105             if let Ok(guard) = self.net.lock() {
    106                 return guard
    107                     .accounts
    108                     .default_signing_identity()
    109                     .ok()
    110                     .flatten()
    111                     .is_some();
    112             }
    113         }
    114 
    115         #[cfg(not(feature = "nostr-client"))]
    116         {
    117             false
    118         }
    119 
    120         #[cfg(feature = "nostr-client")]
    121         false
    122     }
    123 
    124     pub fn nostr_identity_selected_npub(&self) -> Option<String> {
    125         #[cfg(feature = "nostr-client")]
    126         {
    127             if let Ok(guard) = self.net.lock() {
    128                 return guard
    129                     .accounts
    130                     .default_public_identity()
    131                     .ok()
    132                     .flatten()
    133                     .map(|identity| identity.public_key_npub);
    134             }
    135         }
    136 
    137         #[cfg(not(feature = "nostr-client"))]
    138         {
    139             None
    140         }
    141 
    142         #[cfg(feature = "nostr-client")]
    143         None
    144     }
    145 
    146     pub fn nostr_identity_list(&self) -> Result<Vec<NostrIdentityRecord>, RadrootsAppError> {
    147         #[cfg(feature = "nostr-client")]
    148         {
    149             let guard = match self.net.lock() {
    150                 Ok(guard) => guard,
    151                 Err(err) => return Err(RadrootsAppError::runtime(format!("{err}"))),
    152             };
    153             let selected_identity_id = guard
    154                 .accounts
    155                 .default_account_id()
    156                 .map_err(|e| RadrootsAppError::identity(format!("{e}")))?;
    157             let accounts = guard
    158                 .accounts
    159                 .list_accounts()
    160                 .map_err(|e| RadrootsAppError::identity(format!("{e}")))?;
    161             return Ok(accounts
    162                 .into_iter()
    163                 .map(|account| {
    164                     let is_selected = selected_identity_id
    165                         .as_ref()
    166                         .map(|selected| selected == &account.account_id)
    167                         .unwrap_or(false);
    168                     NostrIdentityRecord {
    169                         id: account.account_id.to_string(),
    170                         public_key_hex: account.public_identity.public_key_hex,
    171                         public_key_npub: account.public_identity.public_key_npub,
    172                         label: account.label,
    173                         is_selected,
    174                     }
    175                 })
    176                 .collect());
    177         }
    178         #[cfg(not(feature = "nostr-client"))]
    179         {
    180             Err(RadrootsAppError::unsupported("nostr disabled"))
    181         }
    182     }
    183 
    184     pub fn nostr_identity_list_ids(&self) -> Result<Vec<String>, RadrootsAppError> {
    185         Ok(self
    186             .nostr_identity_list()?
    187             .into_iter()
    188             .map(|identity| identity.id)
    189             .collect())
    190     }
    191 
    192     pub fn nostr_identity_snapshot(&self) -> Result<NostrIdentitySnapshot, RadrootsAppError> {
    193         #[cfg(feature = "nostr-client")]
    194         {
    195             let guard = match self.net.lock() {
    196                 Ok(guard) => guard,
    197                 Err(err) => return Err(RadrootsAppError::runtime(format!("{err}"))),
    198             };
    199             let selected_identity_id = guard
    200                 .accounts
    201                 .default_account_id()
    202                 .map_err(|e| RadrootsAppError::identity(format!("{e}")))?;
    203             let selected_npub = guard
    204                 .accounts
    205                 .default_public_identity()
    206                 .map_err(|e| RadrootsAppError::identity(format!("{e}")))?
    207                 .map(|identity| identity.public_key_npub);
    208             let has_selected_signing_identity = guard
    209                 .accounts
    210                 .default_signing_identity()
    211                 .ok()
    212                 .flatten()
    213                 .is_some();
    214             let identities = guard
    215                 .accounts
    216                 .list_accounts()
    217                 .map_err(|e| RadrootsAppError::identity(format!("{e}")))?
    218                 .into_iter()
    219                 .map(|account| {
    220                     let is_selected = selected_identity_id
    221                         .as_ref()
    222                         .map(|selected| selected == &account.account_id)
    223                         .unwrap_or(false);
    224                     NostrIdentityRecord {
    225                         id: account.account_id.to_string(),
    226                         public_key_hex: account.public_identity.public_key_hex,
    227                         public_key_npub: account.public_identity.public_key_npub,
    228                         label: account.label,
    229                         is_selected,
    230                     }
    231                 })
    232                 .collect();
    233             return Ok(NostrIdentitySnapshot {
    234                 has_selected_signing_identity,
    235                 selected_identity_id: selected_identity_id.map(|id| id.to_string()),
    236                 selected_npub,
    237                 identities,
    238             });
    239         }
    240         #[cfg(not(feature = "nostr-client"))]
    241         {
    242             Err(RadrootsAppError::unsupported("nostr disabled"))
    243         }
    244     }
    245 
    246     pub fn nostr_identity_validate_host_custody_secret(
    247         &self,
    248         secret_key: String,
    249     ) -> Result<NostrHostCustodyIdentity, RadrootsAppError> {
    250         #[cfg(feature = "nostr-client")]
    251         {
    252             return host_custody_identity_from_secret(secret_key.as_str())
    253                 .map(|(_, identity)| identity);
    254         }
    255         #[cfg(not(feature = "nostr-client"))]
    256         {
    257             let _ = secret_key;
    258             Err(RadrootsAppError::unsupported("nostr disabled"))
    259         }
    260     }
    261 
    262     pub fn nostr_identity_restore_host_custody_secret(
    263         &self,
    264         secret_key: String,
    265         label: Option<String>,
    266         make_selected: bool,
    267     ) -> Result<NostrIdentityRecord, RadrootsAppError> {
    268         #[cfg(feature = "nostr-client")]
    269         {
    270             let mut guard = match self.net.lock() {
    271                 Ok(guard) => guard,
    272                 Err(err) => return Err(RadrootsAppError::runtime(format!("{err}"))),
    273             };
    274             let (identity, _) = host_custody_identity_from_secret(secret_key.as_str())?;
    275             return restore_host_custody_identity(&mut guard, &identity, label, make_selected);
    276         }
    277         #[cfg(not(feature = "nostr-client"))]
    278         {
    279             let _ = (secret_key, label, make_selected);
    280             Err(RadrootsAppError::unsupported("nostr disabled"))
    281         }
    282     }
    283 
    284     pub fn nostr_identity_select(&self, identity_id: String) -> Result<(), RadrootsAppError> {
    285         #[cfg(feature = "nostr-client")]
    286         {
    287             let mut guard = match self.net.lock() {
    288                 Ok(guard) => guard,
    289                 Err(err) => return Err(RadrootsAppError::runtime(format!("{err}"))),
    290             };
    291             let account_id = RadrootsIdentityId::parse(identity_id.as_str())
    292                 .map_err(|e| RadrootsAppError::identity(format!("{e}")))?;
    293             guard
    294                 .accounts
    295                 .set_default_account(&account_id)
    296                 .map_err(|e| RadrootsAppError::identity(format!("{e}")))?;
    297             invalidate_nostr_runtime(&mut guard);
    298             Ok(())
    299         }
    300         #[cfg(not(feature = "nostr-client"))]
    301         {
    302             let _ = identity_id;
    303             Err(RadrootsAppError::unsupported("nostr disabled"))
    304         }
    305     }
    306 
    307     pub fn nostr_identity_remove(&self, identity_id: String) -> Result<(), RadrootsAppError> {
    308         #[cfg(feature = "nostr-client")]
    309         {
    310             let mut guard = match self.net.lock() {
    311                 Ok(guard) => guard,
    312                 Err(err) => return Err(RadrootsAppError::runtime(format!("{err}"))),
    313             };
    314             let account_id = RadrootsIdentityId::parse(identity_id.as_str())
    315                 .map_err(|e| RadrootsAppError::identity(format!("{e}")))?;
    316             guard
    317                 .accounts
    318                 .remove_account(&account_id)
    319                 .map_err(|e| RadrootsAppError::identity(format!("{e}")))?;
    320             invalidate_nostr_runtime(&mut guard);
    321             Ok(())
    322         }
    323         #[cfg(not(feature = "nostr-client"))]
    324         {
    325             let _ = identity_id;
    326             Err(RadrootsAppError::unsupported("nostr disabled"))
    327         }
    328     }
    329 
    330     pub fn nostr_identity_lock_host_custody_runtime(&self) -> Result<(), RadrootsAppError> {
    331         #[cfg(feature = "nostr-client")]
    332         {
    333             let mut guard = match self.net.lock() {
    334                 Ok(guard) => guard,
    335                 Err(err) => return Err(RadrootsAppError::runtime(format!("{err}"))),
    336             };
    337             let accounts = guard
    338                 .accounts
    339                 .list_accounts()
    340                 .map_err(|e| RadrootsAppError::identity(format!("{e}")))?;
    341             for account in accounts {
    342                 guard
    343                     .accounts
    344                     .remove_account(&account.account_id)
    345                     .map_err(|e| RadrootsAppError::identity(format!("{e}")))?;
    346             }
    347             guard
    348                 .accounts
    349                 .clear_default_account()
    350                 .map_err(|e| RadrootsAppError::identity(format!("{e}")))?;
    351             invalidate_nostr_runtime(&mut guard);
    352             if let Ok(mut rx_guard) = self.post_events_rx.lock() {
    353                 *rx_guard = None;
    354             }
    355             Ok(())
    356         }
    357         #[cfg(not(feature = "nostr-client"))]
    358         {
    359             Err(RadrootsAppError::unsupported("nostr disabled"))
    360         }
    361     }
    362 
    363     pub fn nostr_identity_reset_host_custody_runtime(&self) -> Result<(), RadrootsAppError> {
    364         self.nostr_identity_lock_host_custody_runtime()
    365     }
    366 }