app

Local-first trade for farms and co-ops
git clone https://radroots.dev/git/app.git
Log | Files | Refs | README | LICENSE

commit cee710880cf5a40687412feba0c2ccb8d6578f5d
parent 6c6e736eb1f6cdd518e905889a2cd205aa6f4e40
Author: triesap <tyson@radroots.org>
Date:   Sat, 21 Mar 2026 19:38:50 +0000

desktop: add secret-key import for local identities

- rename the shared backup and import language from recovery key to secret key
- add the macos desktop setup action to import a local identity from an nsec secret key
- wire the imported identity through the existing accounts manager and select it immediately
- keep ios android web and the lifecycle doc aligned with the corrected secret-key terminology

Diffstat:
Mcrates/android/src/lib.rs | 40++++++++++++++++++++--------------------
Mcrates/core/src/lib.rs | 178++++++++++++++++++++++++++++++++++++++-----------------------------------------
Mcrates/desktop/src/main.rs | 99+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------
Mcrates/ios/src/lib.rs | 23+++++++++++------------
Mcrates/web/src/lib.rs | 2+-
5 files changed, 205 insertions(+), 137 deletions(-)

diff --git a/crates/android/src/lib.rs b/crates/android/src/lib.rs @@ -80,13 +80,13 @@ impl RadrootsAppBackend for AndroidBackend { fn home_action_states(&self) -> Vec<HomeActionState> { #[cfg(target_os = "android")] { - let recovery_key_export_pending = Self::recovery_key_export_pending(); + let secret_key_export_pending = Self::secret_key_export_pending(); return vec![ HomeActionState { - kind: HomeActionKind::BackupRecoveryKey, - label: "Back Up Recovery Key".to_owned(), - enabled: !recovery_key_export_pending, - pending: recovery_key_export_pending, + kind: HomeActionKind::BackupSecretKey, + label: "Back Up Secret Key".to_owned(), + enabled: !secret_key_export_pending, + pending: secret_key_export_pending, }, HomeActionState { kind: HomeActionKind::RemoveLocalKey, @@ -113,8 +113,8 @@ impl RadrootsAppBackend for AndroidBackend { #[cfg(target_os = "android")] { return match action { - HomeActionKind::BackupRecoveryKey => { - Self::begin_recovery_key_export().map(|()| HomeActionResult::None) + HomeActionKind::BackupSecretKey => { + Self::begin_secret_key_export().map(|()| HomeActionResult::None) } HomeActionKind::RemoveLocalKey => { let manager = Self::accounts_manager()?; @@ -141,7 +141,7 @@ impl RadrootsAppBackend for AndroidBackend { fn poll_home_action_result(&self) -> Result<Option<HomeActionResult>, String> { #[cfg(target_os = "android")] { - return Self::poll_recovery_key_export(); + return Self::poll_secret_key_export(); } #[cfg(not(target_os = "android"))] @@ -214,7 +214,7 @@ impl AndroidBackend { Self::identity_state_from_manager(manager) } - fn export_selected_local_recovery_key( + fn export_selected_local_secret_key( manager: &RadrootsNostrAccountsManager, ) -> Result<String, String> { let Some(account_id) = manager @@ -238,35 +238,35 @@ impl AndroidBackend { } #[cfg(target_os = "android")] - fn begin_recovery_key_export() -> Result<(), String> { - security::begin_user_presence_verification("reveal the current recovery key") + fn begin_secret_key_export() -> Result<(), String> { + security::begin_user_presence_verification("reveal the current secret key") .map_err(|source| source.to_string()) } #[cfg(not(target_os = "android"))] - fn begin_recovery_key_export() -> Result<(), String> { + fn begin_secret_key_export() -> Result<(), String> { Ok(()) } #[cfg(target_os = "android")] - fn recovery_key_export_pending() -> bool { + fn secret_key_export_pending() -> bool { security::is_user_presence_verification_pending().unwrap_or(false) } #[cfg(not(target_os = "android"))] - fn recovery_key_export_pending() -> bool { + fn secret_key_export_pending() -> bool { false } #[cfg(target_os = "android")] - fn poll_recovery_key_export() -> Result<Option<HomeActionResult>, String> { + fn poll_secret_key_export() -> Result<Option<HomeActionResult>, String> { match security::take_user_presence_verification_result() .map_err(|source| source.to_string())? { Some(security::AndroidUserPresenceVerificationResult::Verified) => { let manager = Self::accounts_manager()?; - Self::export_selected_local_recovery_key(&manager) - .map(|nsec| Some(HomeActionResult::RevealRecoveryKey { nsec })) + Self::export_selected_local_secret_key(&manager) + .map(|nsec| Some(HomeActionResult::RevealSecretKey { nsec })) } Some(security::AndroidUserPresenceVerificationResult::Failed(message)) => Err(message), None => Ok(None), @@ -274,7 +274,7 @@ impl AndroidBackend { } #[cfg(not(target_os = "android"))] - fn poll_recovery_key_export() -> Result<Option<HomeActionResult>, String> { + fn poll_secret_key_export() -> Result<Option<HomeActionResult>, String> { Ok(None) } @@ -494,7 +494,7 @@ mod tests { } #[test] - fn export_selected_local_recovery_key_returns_nsec() { + fn export_selected_local_secret_key_returns_nsec() { let manager = RadrootsNostrAccountsManager::new_in_memory(); let identity = RadrootsIdentity::generate(); @@ -503,7 +503,7 @@ mod tests { .expect("store identity"); let nsec = - AndroidBackend::export_selected_local_recovery_key(&manager).expect("export recovery"); + AndroidBackend::export_selected_local_secret_key(&manager).expect("export secret"); assert_eq!(nsec, identity.nsec()); assert!(nsec.starts_with("nsec1")); diff --git a/crates/core/src/lib.rs b/crates/core/src/lib.rs @@ -13,7 +13,7 @@ pub struct SetupActionState { } #[derive(Debug, Clone, PartialEq, Eq)] -pub struct RecoveryActionState { +pub struct ImportActionState { pub label: String, pub enabled: bool, pub pending: bool, @@ -29,7 +29,7 @@ pub struct HomeActionState { #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum HomeActionKind { - BackupRecoveryKey, + BackupSecretKey, RemoveLocalKey, ResetDevice, DisconnectSigner, @@ -39,7 +39,7 @@ pub enum HomeActionKind { pub enum HomeActionResult { None, IdentityState(IdentityGateState), - RevealRecoveryKey { nsec: String }, + RevealSecretKey { nsec: String }, } #[derive(Debug, Clone, PartialEq, Eq)] @@ -53,12 +53,12 @@ pub trait RadrootsAppBackend { fn load_identity_state(&self) -> Result<IdentityGateState, String>; fn setup_action_state(&self) -> SetupActionState; fn request_setup_action(&self) -> Result<Option<IdentityGateState>, String>; - fn recovery_action_state(&self) -> Option<RecoveryActionState> { + fn import_action_state(&self) -> Option<ImportActionState> { None } - fn request_recovery_action( + fn request_import_action( &self, - _recovery_key: &str, + _secret_key: &str, ) -> Result<Option<IdentityGateState>, String> { Ok(None) } @@ -87,9 +87,9 @@ pub struct RadrootsApp { screen: AppScreen, status_message: Option<String>, pending_home_confirmation: Option<HomeActionKind>, - pending_recovery_entry: bool, - recovery_key_input: Zeroizing<String>, - revealed_recovery_key: Option<Zeroizing<String>>, + pending_import_entry: bool, + secret_key_input: Zeroizing<String>, + revealed_secret_key: Option<Zeroizing<String>>, } impl RadrootsApp { @@ -99,9 +99,9 @@ impl RadrootsApp { screen: AppScreen::Setup, status_message: None, pending_home_confirmation: None, - pending_recovery_entry: false, - recovery_key_input: Zeroizing::new(String::new()), - revealed_recovery_key: None, + pending_import_entry: false, + secret_key_input: Zeroizing::new(String::new()), + revealed_secret_key: None, }; match app.backend.load_identity_state() { Ok(state) => app.apply_identity_state(state), @@ -119,32 +119,32 @@ impl RadrootsApp { self.screen = AppScreen::Setup; self.status_message = None; self.pending_home_confirmation = None; - self.pending_recovery_entry = false; - self.recovery_key_input.clear(); - self.revealed_recovery_key = None; + self.pending_import_entry = false; + self.secret_key_input.clear(); + self.revealed_secret_key = None; } IdentityGateState::Ready { account_id, npub } => { self.screen = AppScreen::Home { account_id, npub }; self.status_message = None; self.pending_home_confirmation = None; - self.pending_recovery_entry = false; - self.recovery_key_input.clear(); - self.revealed_recovery_key = None; + self.pending_import_entry = false; + self.secret_key_input.clear(); + self.revealed_secret_key = None; } IdentityGateState::Unsupported { reason } => { self.screen = AppScreen::Setup; self.status_message = Some(reason); self.pending_home_confirmation = None; - self.pending_recovery_entry = false; - self.recovery_key_input.clear(); - self.revealed_recovery_key = None; + self.pending_import_entry = false; + self.secret_key_input.clear(); + self.revealed_secret_key = None; } } } fn request_setup_action(&mut self) { self.status_message = None; - self.revealed_recovery_key = None; + self.revealed_secret_key = None; match self.backend.request_setup_action() { Ok(Some(state)) => self.apply_identity_state(state), Ok(None) => {} @@ -154,12 +154,12 @@ impl RadrootsApp { } } - fn request_recovery_action(&mut self) { + fn request_import_action(&mut self) { self.status_message = None; - self.revealed_recovery_key = None; + self.revealed_secret_key = None; match self .backend - .request_recovery_action(self.recovery_key_input.trim()) + .request_import_action(self.secret_key_input.trim()) { Ok(Some(state)) => self.apply_identity_state(state), Ok(None) => {} @@ -171,7 +171,7 @@ impl RadrootsApp { fn request_home_action(&mut self, action: HomeActionKind) { self.status_message = None; - self.revealed_recovery_key = None; + self.revealed_secret_key = None; match self.backend.request_home_action(action) { Ok(result) => self.apply_home_action_result(result), Err(err) => { @@ -183,8 +183,8 @@ impl RadrootsApp { fn apply_home_action_result(&mut self, result: HomeActionResult) { match result { HomeActionResult::IdentityState(state) => self.apply_identity_state(state), - HomeActionResult::RevealRecoveryKey { nsec } => { - self.revealed_recovery_key = Some(Zeroizing::new(nsec)); + HomeActionResult::RevealSecretKey { nsec } => { + self.revealed_secret_key = Some(Zeroizing::new(nsec)); self.pending_home_confirmation = None; } HomeActionResult::None => {} @@ -192,13 +192,13 @@ impl RadrootsApp { } fn home_action_requires_confirmation(action: HomeActionKind) -> bool { - !matches!(action, HomeActionKind::BackupRecoveryKey) + !matches!(action, HomeActionKind::BackupSecretKey) } fn home_action_confirmation_message(action: HomeActionKind) -> &'static str { match action { - HomeActionKind::BackupRecoveryKey => { - "This reveals the current local recovery key for backup. Do not share it." + HomeActionKind::BackupSecretKey => { + "This reveals the current local secret key for backup. Do not share it." } HomeActionKind::RemoveLocalKey => { "This removes the current key from this device and returns the app to setup." @@ -246,9 +246,9 @@ impl eframe::App for RadrootsApp { if action.pending { ctx.request_repaint(); } - let recovery_action = self.backend.recovery_action_state(); - if let Some(recovery_action) = &recovery_action { - if recovery_action.pending { + let import_action = self.backend.import_action_state(); + if let Some(import_action) = &import_action { + if import_action.pending { ctx.request_repaint(); } } @@ -264,17 +264,17 @@ impl eframe::App for RadrootsApp { self.request_setup_action(); } - if let Some(recovery_action) = recovery_action { + if let Some(import_action) = import_action { ui.add_space(12.0); - if self.pending_recovery_entry { + if self.pending_import_entry { ui.vertical_centered(|ui| { ui.set_max_width(ui.available_width().min(560.0)); ui.label( - "Recover an existing local identity by entering its nsec recovery key.", + "Import an existing local identity by entering its nsec secret key.", ); ui.add_space(8.0); ui.add( - egui::TextEdit::singleline(&mut *self.recovery_key_input) + egui::TextEdit::singleline(&mut *self.secret_key_input) .hint_text("nsec1...") .desired_width(ui.available_width()), ); @@ -282,23 +282,23 @@ impl eframe::App for RadrootsApp { ui.horizontal_centered(|ui| { let confirm_clicked = ui .add_enabled( - recovery_action.enabled, - egui::Button::new(recovery_action.label.clone()), + import_action.enabled, + egui::Button::new(import_action.label.clone()), ) .clicked(); if confirm_clicked { - self.request_recovery_action(); + self.request_import_action(); } if ui.button("Cancel").clicked() { - self.pending_recovery_entry = false; - self.recovery_key_input.clear(); + self.pending_import_entry = false; + self.secret_key_input.clear(); self.status_message = None; } }); }); - } else if ui.button(recovery_action.label).clicked() { - self.pending_recovery_entry = true; + } else if ui.button(import_action.label).clicked() { + self.pending_import_entry = true; self.status_message = None; } } @@ -359,14 +359,14 @@ impl eframe::App for RadrootsApp { } } - if let Some(nsec) = &self.revealed_recovery_key { + if let Some(nsec) = &self.revealed_secret_key { ui.add_space(20.0); - ui.label("Recovery key"); + ui.label("Secret key"); ui.add_space(8.0); ui.monospace(nsec.as_str()); ui.add_space(8.0); - if ui.button("Dismiss Recovery Key").clicked() { - self.revealed_recovery_key = None; + if ui.button("Dismiss Secret Key").clicked() { + self.revealed_secret_key = None; } } } @@ -392,10 +392,10 @@ mod tests { struct MockBackend { load: Result<IdentityGateState, String>, action_state: Rc<RefCell<SetupActionState>>, - recovery_action_state: Rc<RefCell<Option<RecoveryActionState>>>, + import_action_state: Rc<RefCell<Option<ImportActionState>>>, home_action_states: Rc<RefCell<Vec<HomeActionState>>>, request: Rc<RefCell<VecDeque<Result<Option<IdentityGateState>, String>>>>, - recovery_request: Rc<RefCell<VecDeque<Result<Option<IdentityGateState>, String>>>>, + import_request: Rc<RefCell<VecDeque<Result<Option<IdentityGateState>, String>>>>, home_request: Rc<RefCell<VecDeque<(HomeActionKind, Result<HomeActionResult, String>)>>>, home_poll: Rc<RefCell<VecDeque<Result<Option<HomeActionResult>, String>>>>, poll: Rc<RefCell<VecDeque<Result<Option<IdentityGateState>, String>>>>, @@ -411,23 +411,23 @@ mod tests { Self { load, action_state: Rc::new(RefCell::new(action_state)), - recovery_action_state: Rc::new(RefCell::new(None)), + import_action_state: Rc::new(RefCell::new(None)), home_action_states: Rc::new(RefCell::new(Vec::new())), request: Rc::new(RefCell::new(request.into())), - recovery_request: Rc::new(RefCell::new(VecDeque::new())), + import_request: Rc::new(RefCell::new(VecDeque::new())), home_request: Rc::new(RefCell::new(VecDeque::new())), home_poll: Rc::new(RefCell::new(VecDeque::new())), poll: Rc::new(RefCell::new(poll.into())), } } - fn with_recovery_action( + fn with_import_action( self, - action_state: RecoveryActionState, + action_state: ImportActionState, request: Vec<Result<Option<IdentityGateState>, String>>, ) -> Self { - *self.recovery_action_state.borrow_mut() = Some(action_state); - self.recovery_request.borrow_mut().extend(request); + *self.import_action_state.borrow_mut() = Some(action_state); + self.import_request.borrow_mut().extend(request); self } @@ -472,15 +472,15 @@ mod tests { .unwrap_or_else(|| Err("missing request response".into())) } - fn recovery_action_state(&self) -> Option<RecoveryActionState> { - self.recovery_action_state.borrow().clone() + fn import_action_state(&self) -> Option<ImportActionState> { + self.import_action_state.borrow().clone() } - fn request_recovery_action( + fn request_import_action( &self, - _recovery_key: &str, + _secret_key: &str, ) -> Result<Option<IdentityGateState>, String> { - self.recovery_request + self.import_request .borrow_mut() .pop_front() .unwrap_or(Ok(None)) @@ -780,7 +780,7 @@ mod tests { } #[test] - fn recovery_action_transitions_to_home() { + fn import_action_transitions_to_home() { let mut app = RadrootsApp::new(Box::new( MockBackend::new( Ok(IdentityGateState::Missing), @@ -792,9 +792,9 @@ mod tests { pending: false, }, ) - .with_recovery_action( - RecoveryActionState { - label: "Recover Existing Key".into(), + .with_import_action( + ImportActionState { + label: "Import Secret Key".into(), enabled: true, pending: false, }, @@ -805,9 +805,9 @@ mod tests { ), )); - app.pending_recovery_entry = true; - app.recovery_key_input = Zeroizing::new("nsec1example".into()); - app.request_recovery_action(); + app.pending_import_entry = true; + app.secret_key_input = Zeroizing::new("nsec1example".into()); + app.request_import_action(); assert_eq!( app.screen, @@ -816,12 +816,12 @@ mod tests { npub: "npub1abc".into(), } ); - assert_eq!(app.pending_recovery_entry, false); - assert_eq!(app.recovery_key_input.as_str(), ""); + assert_eq!(app.pending_import_entry, false); + assert_eq!(app.secret_key_input.as_str(), ""); } #[test] - fn backup_home_action_reveals_recovery_key_without_leaving_home() { + fn backup_home_action_reveals_secret_key_without_leaving_home() { let mut app = RadrootsApp::new(Box::new( MockBackend::new( Ok(IdentityGateState::Ready { @@ -838,31 +838,29 @@ mod tests { ) .with_home_action( HomeActionState { - kind: HomeActionKind::BackupRecoveryKey, - label: "Back Up Recovery Key".into(), + kind: HomeActionKind::BackupSecretKey, + label: "Back Up Secret Key".into(), enabled: true, pending: false, }, - vec![Ok(HomeActionResult::RevealRecoveryKey { + vec![Ok(HomeActionResult::RevealSecretKey { nsec: "nsec1example".into(), })], ), )); - app.request_home_action(HomeActionKind::BackupRecoveryKey); + app.request_home_action(HomeActionKind::BackupSecretKey); assert!(matches!(app.screen, AppScreen::Home { .. })); assert_eq!(app.pending_home_confirmation, None); assert_eq!( - app.revealed_recovery_key - .as_ref() - .map(|value| value.as_str()), + app.revealed_secret_key.as_ref().map(|value| value.as_str()), Some("nsec1example") ); } #[test] - fn deferred_backup_home_action_reveals_recovery_key_after_poll() { + fn deferred_backup_home_action_reveals_secret_key_after_poll() { let mut app = RadrootsApp::new(Box::new( MockBackend::new( Ok(IdentityGateState::Ready { @@ -879,29 +877,25 @@ mod tests { ) .with_home_action( HomeActionState { - kind: HomeActionKind::BackupRecoveryKey, - label: "Back Up Recovery Key".into(), + kind: HomeActionKind::BackupSecretKey, + label: "Back Up Secret Key".into(), enabled: true, pending: true, }, vec![Ok(HomeActionResult::None)], ) - .with_home_action_poll(vec![Ok(Some( - HomeActionResult::RevealRecoveryKey { - nsec: "nsec1example".into(), - }, - ))]), + .with_home_action_poll(vec![Ok(Some(HomeActionResult::RevealSecretKey { + nsec: "nsec1example".into(), + }))]), )); - app.request_home_action(HomeActionKind::BackupRecoveryKey); - assert_eq!(app.revealed_recovery_key, None); + app.request_home_action(HomeActionKind::BackupSecretKey); + assert_eq!(app.revealed_secret_key, None); app.sync_backend(); assert_eq!( - app.revealed_recovery_key - .as_ref() - .map(|value| value.as_str()), + app.revealed_secret_key.as_ref().map(|value| value.as_str()), Some("nsec1example") ); } diff --git a/crates/desktop/src/main.rs b/crates/desktop/src/main.rs @@ -9,8 +9,8 @@ use radroots_app_apple_security::{ APPLE_NOSTR_SERVICE, RadrootsAppleKeychainVault, verify_user_presence, }; use radroots_app_core::{ - APP_NAME, HomeActionKind, HomeActionResult, HomeActionState, IdentityGateState, RadrootsApp, - RadrootsAppBackend, SetupActionState, + APP_NAME, HomeActionKind, HomeActionResult, HomeActionState, IdentityGateState, + ImportActionState, RadrootsApp, RadrootsAppBackend, SetupActionState, }; #[cfg(target_os = "macos")] use radroots_identity::RadrootsIdentity; @@ -145,10 +145,10 @@ impl DesktopBackend { } #[cfg(target_os = "macos")] - fn export_selected_local_recovery_key( + fn export_selected_local_secret_key( manager: &RadrootsNostrAccountsManager, ) -> Result<String, String> { - verify_user_presence("reveal the current recovery key") + verify_user_presence("reveal the current secret key") .map_err(|source| source.to_string())?; let Some(account_id) = manager @@ -172,6 +172,24 @@ impl DesktopBackend { } #[cfg(target_os = "macos")] + fn import_local_identity( + manager: &RadrootsNostrAccountsManager, + secret_key: &str, + ) -> Result<IdentityGateState, String> { + let identity = RadrootsIdentity::from_secret_key_str(secret_key) + .map_err(|_| "invalid secret key".to_owned())?; + + manager + .upsert_identity(&identity, None, true) + .map_err(|source| source.to_string())?; + + let status = manager + .selected_account_status() + .map_err(|source| source.to_string())?; + Ok(Self::map_status(status)) + } + + #[cfg(target_os = "macos")] fn remove_all_local_identities( manager: &RadrootsNostrAccountsManager, ) -> Result<IdentityGateState, String> { @@ -273,13 +291,43 @@ impl RadrootsAppBackend for DesktopBackend { } } + fn import_action_state(&self) -> Option<ImportActionState> { + #[cfg(target_os = "macos")] + { + return Some(ImportActionState { + label: "Import Secret Key".to_owned(), + enabled: true, + pending: false, + }); + } + + #[cfg(not(target_os = "macos"))] + { + None + } + } + + fn request_import_action(&self, secret_key: &str) -> Result<Option<IdentityGateState>, String> { + #[cfg(target_os = "macos")] + { + let manager = Self::accounts_manager()?; + return Self::import_local_identity(&manager, secret_key).map(Some); + } + + #[cfg(not(target_os = "macos"))] + { + let _ = secret_key; + Ok(None) + } + } + fn home_action_states(&self) -> Vec<HomeActionState> { #[cfg(target_os = "macos")] { return vec![ HomeActionState { - kind: HomeActionKind::BackupRecoveryKey, - label: "Back Up Recovery Key".to_owned(), + kind: HomeActionKind::BackupSecretKey, + label: "Back Up Secret Key".to_owned(), enabled: true, pending: false, }, @@ -309,10 +357,8 @@ impl RadrootsAppBackend for DesktopBackend { { let manager = Self::accounts_manager()?; return match action { - HomeActionKind::BackupRecoveryKey => { - Self::export_selected_local_recovery_key(&manager) - .map(|nsec| HomeActionResult::RevealRecoveryKey { nsec }) - } + HomeActionKind::BackupSecretKey => Self::export_selected_local_secret_key(&manager) + .map(|nsec| HomeActionResult::RevealSecretKey { nsec }), HomeActionKind::RemoveLocalKey => Self::remove_selected_local_identity(&manager) .map(HomeActionResult::IdentityState), HomeActionKind::ResetDevice => { @@ -434,7 +480,7 @@ mod tests { } #[test] - fn export_selected_local_recovery_key_returns_nsec() { + fn export_selected_local_secret_key_returns_nsec() { let manager = radroots_nostr_accounts::prelude::RadrootsNostrAccountsManager::new_in_memory(); let identity = RadrootsIdentity::generate(); @@ -444,13 +490,42 @@ mod tests { .expect("store identity"); let nsec = - DesktopBackend::export_selected_local_recovery_key(&manager).expect("export recovery"); + DesktopBackend::export_selected_local_secret_key(&manager).expect("export secret"); assert_eq!(nsec, identity.nsec()); assert!(nsec.starts_with("nsec1")); } #[test] + fn import_local_identity_imports_nsec_and_selects_account() { + let manager = + radroots_nostr_accounts::prelude::RadrootsNostrAccountsManager::new_in_memory(); + let identity = RadrootsIdentity::generate(); + + let state = DesktopBackend::import_local_identity(&manager, identity.nsec().as_str()) + .expect("import identity"); + + assert_eq!( + state, + radroots_app_core::IdentityGateState::Ready { + account_id: identity.id().to_string(), + npub: identity.npub(), + } + ); + assert_eq!( + manager.selected_account_id().expect("selected"), + Some(identity.id()) + ); + assert_eq!(manager.list_accounts().expect("list").len(), 1); + assert_eq!( + manager + .export_secret_hex(&identity.id()) + .expect("export secret"), + Some(identity.secret_key_hex()) + ); + } + + #[test] fn remove_accounts_file_if_present_deletes_existing_file() { let unique = format!( "radroots-desktop-reset-{}-{}.json", diff --git a/crates/ios/src/lib.rs b/crates/ios/src/lib.rs @@ -80,10 +80,10 @@ impl IosBackend { Self::identity_state_from_manager(manager) } - fn export_selected_local_recovery_key( + fn export_selected_local_secret_key( manager: &RadrootsNostrAccountsManager, ) -> Result<String, String> { - Self::authorize_recovery_key_export()?; + Self::authorize_secret_key_export()?; let Some(account_id) = manager .selected_account_id() @@ -106,12 +106,12 @@ impl IosBackend { } #[cfg(target_os = "ios")] - fn authorize_recovery_key_export() -> Result<(), String> { - verify_user_presence("reveal the current recovery key").map_err(|source| source.to_string()) + fn authorize_secret_key_export() -> Result<(), String> { + verify_user_presence("reveal the current secret key").map_err(|source| source.to_string()) } #[cfg(not(target_os = "ios"))] - fn authorize_recovery_key_export() -> Result<(), String> { + fn authorize_secret_key_export() -> Result<(), String> { Ok(()) } @@ -176,8 +176,8 @@ impl RadrootsAppBackend for IosBackend { fn home_action_states(&self) -> Vec<HomeActionState> { vec![ HomeActionState { - kind: HomeActionKind::BackupRecoveryKey, - label: "Back Up Recovery Key".to_owned(), + kind: HomeActionKind::BackupSecretKey, + label: "Back Up Secret Key".to_owned(), enabled: true, pending: false, }, @@ -199,8 +199,8 @@ impl RadrootsAppBackend for IosBackend { fn request_home_action(&self, action: HomeActionKind) -> Result<HomeActionResult, String> { let manager = Self::accounts_manager()?; match action { - HomeActionKind::BackupRecoveryKey => Self::export_selected_local_recovery_key(&manager) - .map(|nsec| HomeActionResult::RevealRecoveryKey { nsec }), + HomeActionKind::BackupSecretKey => Self::export_selected_local_secret_key(&manager) + .map(|nsec| HomeActionResult::RevealSecretKey { nsec }), HomeActionKind::RemoveLocalKey => { Self::remove_selected_local_identity(&manager).map(HomeActionResult::IdentityState) } @@ -326,7 +326,7 @@ mod tests { } #[test] - fn export_selected_local_recovery_key_returns_nsec() { + fn export_selected_local_secret_key_returns_nsec() { let manager = RadrootsNostrAccountsManager::new_in_memory(); let identity = RadrootsIdentity::generate(); @@ -334,8 +334,7 @@ mod tests { .upsert_identity(&identity, Some("primary".into()), true) .expect("store identity"); - let nsec = - IosBackend::export_selected_local_recovery_key(&manager).expect("export recovery"); + let nsec = IosBackend::export_selected_local_secret_key(&manager).expect("export secret"); assert_eq!(nsec, identity.nsec()); assert!(nsec.starts_with("nsec1")); diff --git a/crates/web/src/lib.rs b/crates/web/src/lib.rs @@ -173,7 +173,7 @@ impl RadrootsAppBackend for WebBackend { HomeActionKind::DisconnectSigner => { Ok(HomeActionResult::IdentityState(self.disconnect_signer())) } - HomeActionKind::BackupRecoveryKey + HomeActionKind::BackupSecretKey | HomeActionKind::RemoveLocalKey | HomeActionKind::ResetDevice => Ok(HomeActionResult::None), }