diff --git a/locales/de.yml b/locales/de.yml index 927a354c..cb3b5df3 100644 --- a/locales/de.yml +++ b/locales/de.yml @@ -465,7 +465,6 @@ goblin: connecting_relays: "Verbinde mit Relays…" identity: "Identität" copy_npub: "npub kopieren (öffentlich)" - rotate_key: "nostr-Schlüssel wechseln" import_identity: "Identität importieren (.backup / nsec)" backup_note: "Gerät wechseln? Sichere BEIDES: deine Seed-Phrase (Guthaben) und deine Identitäts-.backup-Datei (Name + Schlüssel)." wallet: "Wallet" @@ -530,25 +529,12 @@ goblin: sp_amount_gt_zero: "Gib einen Betrag größer als null ein." sp_to_send: "Zu sendender slatepack" sp_copy: "Slatepack kopieren" - rotate_line1: "• Du bekommst einen brandneuen ZUFÄLLIGEN Schlüssel; das alte npub empfängt nichts mehr. Es gibt keine Ableitungskette zwischen beiden." - rotate_line2: "• Der neue Schlüssel ist NICHT aus deinem Seed wiederherstellbar — sichere die neue nsec direkt nach dem Wechsel." - rotate_line3: "• Dein Benutzername wird FREIGEGEBEN — beanspruche direkt danach denselben oder einen neuen Namen (sobald frei, kann ihn auch jede andere Person nehmen)." - rotate_line4: "• Zahlungen, die noch an den alten Schlüssel unterwegs sind, WERDEN gestört — warte zuerst, bis ausstehende Zahlungen abgeschlossen sind." - rotate_line5: "• Kontakte, die dein npub direkt gespeichert haben, müssen dich neu finden — teile dein neues npub oder den neu gesicherten username." cancel: "Abbrechen" continue: "Weiter" final_confirmation: "Endgültige Bestätigung" - rotate_confirm_blurb: "Dies kann in der App nicht rückgängig gemacht werden. Tippe RESET und gib dein Wallet-Passwort ein, um zu wechseln." type_reset: "RESET tippen" wallet_password: "Wallet-Passwort" - rotate_key_btn: "Schlüssel wechseln" - rotating_key: "Wechsle Schlüssel…" - key_rotated: "Schlüssel gewechselt" - new_npub: "Neues npub: %{npub}" - backup_new_key: "Sichere jetzt den NEUEN geheimen Schlüssel — dein Seed kann ihn nicht wiederherstellen." - copy_new_nsec: "Neues nsec-Backup kopieren" done: "Fertig" - rotation_failed: "Wechsel fehlgeschlagen" close: "Schließen" import_identity_title: "Identität importieren" import_blurb: "Ersetzt die nostr-Identität dieser Wallet — wähle eine GOBLIN-.backup-Datei oder füge einen nsec ein. Eine Sicherung stellt auch Benutzername und Verlauf wieder her. Sichere zuerst den aktuellen Schlüssel, falls du ihn noch brauchst." diff --git a/locales/en.yml b/locales/en.yml index 38a01c9b..7cb94508 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -465,7 +465,6 @@ goblin: connecting_relays: "Connecting to relays…" identity: "Identity" copy_npub: "Copy npub (public)" - rotate_key: "Rotate nostr key" import_identity: "Import identity (.backup / nsec)" backup_note: "Moving devices? Back up BOTH: your seed phrase (funds) and your identity .backup file (name + key)." wallet: "Wallet" @@ -530,25 +529,12 @@ goblin: sp_amount_gt_zero: "Enter an amount greater than zero." sp_to_send: "Slatepack to send" sp_copy: "Copy slatepack" - rotate_line1: "• You get a brand-new RANDOM key; the old npub stops receiving. There is no derivation chain between them." - rotate_line2: "• The new key is NOT recoverable from your seed — back up the new nsec right after rotating." - rotate_line3: "• Your username is RELEASED — claim the same or a new name right after (anyone else can grab it too once it's free)." - rotate_line4: "• Payments still in flight to the old key WILL be disrupted — wait for pending payments to finish first." - rotate_line5: "• Contacts who saved your npub directly must re-find you — share your new npub or re-claimed username." cancel: "Cancel" continue: "Continue" final_confirmation: "Final confirmation" - rotate_confirm_blurb: "This cannot be undone from the app. Type RESET and enter your wallet password to rotate." type_reset: "Type RESET" wallet_password: "Wallet password" - rotate_key_btn: "Rotate key" - rotating_key: "Rotating key…" - key_rotated: "Key rotated" - new_npub: "New npub: %{npub}" - backup_new_key: "Back up the NEW secret key now — your seed cannot recover it." - copy_new_nsec: "Copy new nsec backup" done: "Done" - rotation_failed: "Rotation failed" close: "Close" import_identity_title: "Import identity" import_blurb: "Replaces this wallet's nostr identity — choose a GOBLIN .backup file, or paste a bare nsec. A backup also restores your username and history. Back up the current key first if you still need it." diff --git a/locales/es.yml b/locales/es.yml index f808d316..4db6c0e6 100644 --- a/locales/es.yml +++ b/locales/es.yml @@ -465,7 +465,6 @@ goblin: connecting_relays: "Conectando a relés…" identity: "Identidad" copy_npub: "Copiar npub (público)" - rotate_key: "Rotar clave nostr" import_identity: "Importar identidad (.backup / nsec)" backup_note: "¿Cambiando de dispositivo? Respalda AMBOS: tu frase de recuperación (fondos) y tu archivo .backup de identidad (nombre + clave)." wallet: "Billetera" @@ -530,25 +529,12 @@ goblin: sp_amount_gt_zero: "Ingresa un monto mayor que cero." sp_to_send: "Slatepack para enviar" sp_copy: "Copiar slatepack" - rotate_line1: "• Obtienes una clave ALEATORIA totalmente nueva; el npub anterior deja de recibir. No hay ninguna cadena de derivación entre ellas." - rotate_line2: "• La nueva clave NO se puede recuperar desde tu frase de recuperación — respalda el nuevo nsec justo después de rotar." - rotate_line3: "• Tu nombre de usuario queda LIBERADO — reclama el mismo u otro nuevo justo después (cualquier otra persona puede tomarlo en cuanto quede libre)." - rotate_line4: "• Los pagos aún en camino hacia la clave anterior SE INTERRUMPIRÁN — espera a que terminen los pagos pendientes antes de continuar." - rotate_line5: "• Los contactos que guardaron tu npub directamente deberán encontrarte de nuevo — comparte tu nuevo npub o tu nombre de usuario reclamado de nuevo." cancel: "Cancelar" continue: "Continuar" final_confirmation: "Confirmación final" - rotate_confirm_blurb: "Esto no se puede deshacer desde la app. Escribe RESET e ingresa tu contraseña de billetera para rotar." type_reset: "Escribe RESET" wallet_password: "Contraseña de la billetera" - rotate_key_btn: "Rotar clave" - rotating_key: "Rotando clave…" - key_rotated: "Clave rotada" - new_npub: "Nuevo npub: %{npub}" - backup_new_key: "Respalda la NUEVA clave secreta ahora — tu frase de recuperación no puede recuperarla." - copy_new_nsec: "Copiar respaldo del nuevo nsec" done: "Listo" - rotation_failed: "Error al rotar" close: "Cerrar" import_identity_title: "Importar identidad" import_blurb: "Reemplaza la identidad nostr de esta billetera — elige un archivo .backup de GOBLIN, o pega un nsec directamente. Un respaldo también restaura tu nombre de usuario e historial. Respalda primero la clave actual si aún la necesitas." diff --git a/locales/fr.yml b/locales/fr.yml index 23d0ddda..9e349b08 100644 --- a/locales/fr.yml +++ b/locales/fr.yml @@ -465,7 +465,6 @@ goblin: connecting_relays: "Connexion aux relais…" identity: "Identité" copy_npub: "Copier le npub (public)" - rotate_key: "Renouveler la clé nostr" import_identity: "Importer l'identité (.backup / nsec)" backup_note: "Changement d'appareil ? Sauvegardez les DEUX : votre phrase seed (fonds) et votre fichier .backup d'identité (nom + clé)." wallet: "Portefeuille" @@ -530,25 +529,12 @@ goblin: sp_amount_gt_zero: "Saisissez un montant supérieur à zéro." sp_to_send: "Slatepack à envoyer" sp_copy: "Copier le slatepack" - rotate_line1: "• Vous obtenez une toute nouvelle clé ALÉATOIRE ; l'ancien npub cesse de recevoir. Il n'y a aucune chaîne de dérivation entre eux." - rotate_line2: "• La nouvelle clé n'est PAS récupérable depuis votre phrase de récupération — sauvegardez le nouveau nsec juste après le renouvellement." - rotate_line3: "• Votre nom d'utilisateur est LIBÉRÉ — réclamez le même ou un nouveau juste après (une fois libre, n'importe qui peut le prendre)." - rotate_line4: "• Les paiements encore en cours vers l'ancienne clé SERONT interrompus — attendez d'abord la fin des paiements en attente." - rotate_line5: "• Les contacts qui ont enregistré votre npub directement doivent vous retrouver — partagez votre nouveau npub ou votre username re-réservé." cancel: "Annuler" continue: "Continuer" final_confirmation: "Confirmation finale" - rotate_confirm_blurb: "Cette action est irréversible depuis l'app. Tapez RESET et saisissez le mot de passe du portefeuille pour renouveler." type_reset: "Tapez RESET" wallet_password: "Mot de passe du portefeuille" - rotate_key_btn: "Renouveler la clé" - rotating_key: "Renouvellement de la clé…" - key_rotated: "Clé renouvelée" - new_npub: "Nouveau npub : %{npub}" - backup_new_key: "Sauvegardez la NOUVELLE clé secrète maintenant — votre phrase de récupération ne peut pas la restaurer." - copy_new_nsec: "Copier la sauvegarde du nouveau nsec" done: "Terminé" - rotation_failed: "Échec du renouvellement" close: "Fermer" import_identity_title: "Importer une identité" import_blurb: "Remplace l'identité nostr de ce portefeuille — choisissez un fichier .backup GOBLIN, ou collez un nsec. Une sauvegarde restaure aussi votre nom d'utilisateur et votre historique. Sauvegardez d'abord la clé actuelle si besoin." diff --git a/locales/ko.yml b/locales/ko.yml index 5a5e241c..803e4708 100644 --- a/locales/ko.yml +++ b/locales/ko.yml @@ -465,7 +465,6 @@ goblin: connecting_relays: "릴레이에 연결하는 중…" identity: "신원" copy_npub: "npub 복사(공개)" - rotate_key: "nostr 키 교체" import_identity: "신원 가져오기(.backup / nsec)" backup_note: "기기를 바꾸시나요? 두 가지 모두 백업하세요: 복구 문구(자금)와 신원 .backup 파일(이름 + 키)." wallet: "지갑" @@ -530,25 +529,12 @@ goblin: sp_amount_gt_zero: "0보다 큰 금액을 입력하세요." sp_to_send: "보낼 slatepack" sp_copy: "slatepack 복사" - rotate_line1: "• 완전히 새로운 무작위 키를 받게 됩니다. 이전 npub은 더 이상 받지 못합니다. 둘 사이에는 어떤 파생 관계도 없습니다." - rotate_line2: "• 새 키는 복구 문구로 복구할 수 없습니다 — 교체 직후 새 nsec을 반드시 백업하세요." - rotate_line3: "• 사용자 이름이 해제됩니다 — 곧바로 같은 이름이나 새 이름을 등록하세요(자유로워지는 순간 다른 사람도 가져갈 수 있습니다)." - rotate_line4: "• 이전 키로 전송 중이던 결제는 중단됩니다 — 대기 중인 결제가 끝날 때까지 먼저 기다리세요." - rotate_line5: "• npub을 직접 저장해 둔 연락처는 나를 다시 찾아야 합니다 — 새 npub이나 다시 등록한 사용자 이름을 공유하세요." cancel: "취소" continue: "계속" final_confirmation: "최종 확인" - rotate_confirm_blurb: "이 작업은 앱에서 되돌릴 수 없습니다. RESET을 입력하고 지갑 비밀번호를 입력해 교체하세요." type_reset: "RESET 입력" wallet_password: "지갑 비밀번호" - rotate_key_btn: "키 교체" - rotating_key: "키 교체하는 중…" - key_rotated: "키가 교체되었습니다" - new_npub: "새 npub: %{npub}" - backup_new_key: "지금 새 비밀 키를 백업하세요 — 복구 문구로는 복구할 수 없습니다." - copy_new_nsec: "새 nsec 백업 복사" done: "완료" - rotation_failed: "교체 실패" close: "닫기" import_identity_title: "신원 가져오기" import_blurb: "이 지갑의 nostr 신원을 대체합니다 — GOBLIN .backup 파일을 선택하거나 nsec을 붙여넣으세요. 백업은 사용자 이름과 기록도 함께 복원합니다. 기존 키가 아직 필요하다면 먼저 백업하세요." diff --git a/locales/ru.yml b/locales/ru.yml index a8cfa8e1..e69f9586 100644 --- a/locales/ru.yml +++ b/locales/ru.yml @@ -465,7 +465,6 @@ goblin: connecting_relays: "Подключение к реле…" identity: "Личность" copy_npub: "Копировать npub (публичный)" - rotate_key: "Сменить ключ nostr" import_identity: "Импорт личности (.backup / nsec)" backup_note: "Меняете устройство? Сохраните ОБА: seed-фразу (средства) и файл .backup личности (имя + ключ)." wallet: "Кошелёк" @@ -530,25 +529,12 @@ goblin: sp_amount_gt_zero: "Введите сумму больше нуля." sp_to_send: "Slatepack для отправки" sp_copy: "Копировать slatepack" - rotate_line1: "• Вы получите совершенно новый СЛУЧАЙНЫЙ ключ; старый npub перестанет принимать. Между ними нет цепочки вывода." - rotate_line2: "• Новый ключ НЕЛЬЗЯ восстановить из seed — сохраните новый nsec сразу после смены." - rotate_line3: "• Ваше имя пользователя ОСВОБОЖДАЕТСЯ — сразу после заявите то же или новое имя (как только свободно, его может занять кто угодно)." - rotate_line4: "• Платежи, всё ещё идущие к старому ключу, БУДУТ нарушены — сначала дождитесь завершения ожидающих платежей." - rotate_line5: "• Контакты, сохранившие ваш npub напрямую, должны найти вас заново — поделитесь новым npub или заново занятым username." cancel: "Отмена" continue: "Продолжить" final_confirmation: "Финальное подтверждение" - rotate_confirm_blurb: "Это нельзя отменить из приложения. Введите RESET и пароль кошелька, чтобы сменить." type_reset: "Введите RESET" wallet_password: "Пароль кошелька" - rotate_key_btn: "Сменить ключ" - rotating_key: "Смена ключа…" - key_rotated: "Ключ сменён" - new_npub: "Новый npub: %{npub}" - backup_new_key: "Сохраните НОВЫЙ секретный ключ сейчас — seed не сможет его восстановить." - copy_new_nsec: "Копировать резерв нового nsec" done: "Готово" - rotation_failed: "Смена не удалась" close: "Закрыть" import_identity_title: "Импорт личности" import_blurb: "Заменяет nostr-личность этого кошелька — выберите файл GOBLIN .backup или вставьте nsec. Резервная копия также восстанавливает имя и историю. Сначала сохраните текущий ключ, если он ещё нужен." diff --git a/locales/tr.yml b/locales/tr.yml index 46adfe2a..38ec35b9 100644 --- a/locales/tr.yml +++ b/locales/tr.yml @@ -465,7 +465,6 @@ goblin: connecting_relays: "Relaylara bağlanılıyor…" identity: "Kimlik" copy_npub: "npub kopyala (genel)" - rotate_key: "nostr anahtarını değiştir" import_identity: "Kimlik içe aktar (.backup / nsec)" backup_note: "Cihaz mı değiştiriyorsun? İKİSİNİ de yedekle: seed ifaden (bakiye) ve kimlik .backup dosyan (ad + anahtar)." wallet: "Cüzdan" @@ -530,25 +529,12 @@ goblin: sp_amount_gt_zero: "Sıfırdan büyük bir tutar gir." sp_to_send: "Gönderilecek slatepack" sp_copy: "Slatepack kopyala" - rotate_line1: "• Tamamen yeni RASTGELE bir anahtar alırsın; eski npub artık almaz. Aralarında türetme zinciri yoktur." - rotate_line2: "• Yeni anahtar tohumundan kurtarılamaz — anahtarı değiştirdikten hemen sonra yeni nsec'i yedekle." - rotate_line3: "• Kullanıcı adın SERBEST BIRAKILIR — hemen ardından aynı adı ya da yeni bir ad al (serbest kaldığında başkası da kapabilir)." - rotate_line4: "• Eski anahtara hâlâ yoldaki ödemeler KESİNTİYE uğrar — önce bekleyen ödemelerin bitmesini bekle." - rotate_line5: "• npub'unu doğrudan kaydeden kişiler seni yeniden bulmalı — yeni npub'unu ya da yeniden aldığın username'i paylaş." cancel: "İptal" continue: "Devam" final_confirmation: "Son onay" - rotate_confirm_blurb: "Bu işlem uygulamadan geri alınamaz. Değiştirmek için RESET yaz ve cüzdan parolanı gir." type_reset: "RESET yaz" wallet_password: "Cüzdan parolası" - rotate_key_btn: "Anahtarı değiştir" - rotating_key: "Anahtar değiştiriliyor…" - key_rotated: "Anahtar değiştirildi" - new_npub: "Yeni npub: %{npub}" - backup_new_key: "YENİ gizli anahtarı şimdi yedekle — tohumun onu kurtaramaz." - copy_new_nsec: "Yeni nsec yedeğini kopyala" done: "Bitti" - rotation_failed: "Değiştirme başarısız" close: "Kapat" import_identity_title: "Kimlik içe aktar" import_blurb: "Bu cüzdanın nostr kimliğini değiştirir — bir GOBLIN .backup dosyası seç ya da nsec yapıştır. Yedek ayrıca kullanıcı adını ve geçmişini geri yükler. Hâlâ gerekiyorsa önce mevcut anahtarı yedekle." diff --git a/locales/zh-CN.yml b/locales/zh-CN.yml index 28dd1d3d..c4910088 100644 --- a/locales/zh-CN.yml +++ b/locales/zh-CN.yml @@ -465,7 +465,6 @@ goblin: connecting_relays: "正在连接中继…" identity: "身份" copy_npub: "复制 npub(公开)" - rotate_key: "轮换 nostr 密钥" import_identity: "导入身份(.backup / nsec)" backup_note: "更换设备?两者都要备份:你的助记词(资金)和身份 .backup 文件(名称 + 密钥)。" wallet: "钱包" @@ -530,25 +529,12 @@ goblin: sp_amount_gt_zero: "请输入大于零的金额。" sp_to_send: "待发送的 slatepack" sp_copy: "复制 slatepack" - rotate_line1: "• 你会得到一个全新的随机密钥;旧 npub 将停止接收。两者之间没有任何派生关系。" - rotate_line2: "• 新密钥无法从助记词恢复 — 轮换后请立即备份新的 nsec。" - rotate_line3: "• 你的用户名将被释放——请立即认领相同或新的名称(一旦释放,他人也可抢注)。" - rotate_line4: "• 正在发往旧密钥的付款将受影响 — 请先等待待处理付款完成。" - rotate_line5: "• 直接保存了你 npub 的联系人需要重新查找你 — 分享你的新 npub 或重新注册的 username。" cancel: "取消" continue: "继续" final_confirmation: "最终确认" - rotate_confirm_blurb: "此操作在应用内无法撤销。输入 RESET 并输入钱包密码以进行轮换。" type_reset: "输入 RESET" wallet_password: "钱包密码" - rotate_key_btn: "轮换密钥" - rotating_key: "正在轮换密钥…" - key_rotated: "密钥已轮换" - new_npub: "新 npub:%{npub}" - backup_new_key: "立即备份新私钥 — 助记词无法恢复它。" - copy_new_nsec: "复制新 nsec 备份" done: "完成" - rotation_failed: "轮换失败" close: "关闭" import_identity_title: "导入身份" import_blurb: "替换此钱包的 nostr 身份——选择一个 GOBLIN .backup 文件,或粘贴 nsec。备份也会恢复你的用户名和历史。如果仍需要当前密钥,请先备份。" diff --git a/src/gui/views/goblin/mod.rs b/src/gui/views/goblin/mod.rs index b9849d0b..0367bf6c 100644 --- a/src/gui/views/goblin/mod.rs +++ b/src/gui/views/goblin/mod.rs @@ -75,8 +75,6 @@ pub struct GoblinWalletView { wallet_id: Option, /// Inline username-claim state for the Me tab. claim: Option, - /// Inline key-rotation state for the Me tab. - rotate: Option, /// Inline nsec-import state for the Me tab. import_nsec: Option, /// Inline "back up identity to a file" flow state. @@ -300,7 +298,6 @@ impl Default for GoblinWalletView { request_error: None, wallet_id: None, claim: None, - rotate: None, import_nsec: None, backup: None, identity_switch: IdentitySwitchState::default(), @@ -340,31 +337,6 @@ impl Default for GoblinWalletView { } } -/// Inline key-rotation flow state (two warnings + typed confirmation). -struct RotateState { - /// 1 = warning, 2 = confirm (RESET + password), 3 = working, - /// 4 = done (new npub), 5 = error (message). - stage: u8, - reset_input: String, - password: String, - new_npub: String, - error: String, - result: std::sync::Arc>>>, -} - -impl Default for RotateState { - fn default() -> Self { - Self { - stage: 1, - reset_input: String::new(), - password: String::new(), - new_npub: String::new(), - error: String::new(), - result: std::sync::Arc::new(std::sync::Mutex::new(None)), - } - } -} - /// Inline nsec-import flow state (restore path for the random-key model). struct ImportState { /// 1 = form, 3 = working, 4 = done, 5 = error. @@ -816,7 +788,6 @@ impl GoblinWalletView { self.receipt = None; self.profile = None; self.claim = None; - self.rotate = None; self.import_nsec = None; self.approve_review = None; self.approve_hold = w::HoldToSend::default(); @@ -3666,14 +3637,6 @@ impl GoblinWalletView { { self.backup = Some(BackupState::default()); } - if settings_row_danger( - ui, - &t!("goblin.settings.rotate_key"), - crate::gui::icons::ARROWS_CLOCKWISE, - ) && self.rotate.is_none() - { - self.rotate = Some(RotateState::default()); - } if settings_row_btn( ui, &t!("goblin.settings.import_identity"), @@ -3754,10 +3717,6 @@ impl GoblinWalletView { ui.add_space(8.0); self.backup_ui(ui, wallet, cb); } - if self.rotate.is_some() { - ui.add_space(8.0); - self.rotate_ui(ui, wallet, cb); - } if self.import_nsec.is_some() { ui.add_space(8.0); self.import_nsec_ui(ui, wallet, cb); @@ -5357,234 +5316,6 @@ impl GoblinWalletView { }); } - /// Inline key-rotation flow: warning → typed RESET + password → result. - fn rotate_ui(&mut self, ui: &mut egui::Ui, wallet: &Wallet, cb: &dyn PlatformCallbacks) { - let t = theme::tokens(); - let rotate = self.rotate.as_mut().unwrap(); - // Poll the worker result. - if rotate.stage == 3 { - if let Some(res) = rotate.result.lock().unwrap().take() { - match res { - Ok(npub) => { - rotate.new_npub = npub; - rotate.stage = 4; - } - Err(e) => { - rotate.error = e; - rotate.stage = 5; - } - } - } - } - let mut close = false; - w::card(ui, |ui| { - ui.set_min_width(ui.available_width()); - match rotate.stage { - 1 => { - ui.label( - RichText::new(t!("goblin.settings.rotate_key")) - .font(FontId::new(15.0, fonts::semibold())) - .color(t.neg), - ); - ui.add_space(6.0); - for line in [ - t!("goblin.settings.rotate_line1"), - t!("goblin.settings.rotate_line2"), - t!("goblin.settings.rotate_line3"), - t!("goblin.settings.rotate_line4"), - t!("goblin.settings.rotate_line5"), - ] { - ui.label( - RichText::new(line) - .font(FontId::new(13.0, fonts::regular())) - .color(t.surface_text_dim), - ); - ui.add_space(4.0); - } - ui.add_space(8.0); - ui.horizontal(|ui| { - let half = (ui.available_width() - 10.0) / 2.0; - ui.scope_builder( - egui::UiBuilder::new().max_rect(egui::Rect::from_min_size( - ui.cursor().min, - Vec2::new(half, 44.0), - )), - |ui| { - if w::big_action_on_card(ui, &t!("goblin.settings.cancel")) - .clicked() - { - close = true; - } - }, - ); - ui.add_space(10.0); - ui.scope_builder( - egui::UiBuilder::new().max_rect(egui::Rect::from_min_size( - ui.cursor().min, - Vec2::new(half, 44.0), - )), - |ui| { - if w::big_action(ui, &t!("goblin.settings.continue"), false) - .clicked() - { - rotate.stage = 2; - } - }, - ); - }); - } - 2 => { - ui.label( - RichText::new(t!("goblin.settings.final_confirmation")) - .font(FontId::new(15.0, fonts::semibold())) - .color(t.neg), - ); - ui.add_space(6.0); - ui.label( - RichText::new(t!("goblin.settings.rotate_confirm_blurb")) - .font(FontId::new(13.0, fonts::regular())) - .color(t.surface_text_dim), - ); - ui.add_space(10.0); - w::field_well(ui, |ui| { - TextEdit::new(egui::Id::from("rotate_reset")) - .focus(false) - .hint_text(t!("goblin.settings.type_reset")) - .text_color(t.surface_text) - .body() - .ui(ui, &mut rotate.reset_input, cb); - }); - ui.add_space(8.0); - w::field_well(ui, |ui| { - TextEdit::new(egui::Id::from("rotate_pass")) - .focus(false) - .hint_text(t!("goblin.settings.wallet_password")) - .password() - .text_color(t.surface_text) - .body() - .ui(ui, &mut rotate.password, cb); - }); - ui.add_space(10.0); - let armed = rotate.reset_input.trim() == "RESET" && !rotate.password.is_empty(); - ui.horizontal(|ui| { - let half = (ui.available_width() - 10.0) / 2.0; - ui.scope_builder( - egui::UiBuilder::new().max_rect(egui::Rect::from_min_size( - ui.cursor().min, - Vec2::new(half, 44.0), - )), - |ui| { - if w::big_action_on_card(ui, &t!("goblin.settings.cancel")) - .clicked() - { - close = true; - } - }, - ); - ui.add_space(10.0); - ui.scope_builder( - egui::UiBuilder::new().max_rect(egui::Rect::from_min_size( - ui.cursor().min, - Vec2::new(half, 44.0), - )), - |ui| { - ui.add_enabled_ui(armed, |ui| { - if w::big_action( - ui, - &t!("goblin.settings.rotate_key_btn"), - false, - ) - .clicked() - { - rotate.stage = 3; - let slot = rotate.result.clone(); - let password = std::mem::take(&mut rotate.password); - rotate.reset_input.clear(); - let wallet = wallet.clone(); - std::thread::spawn(move || { - let res = wallet.rotate_nostr_identity(password); - *slot.lock().unwrap() = Some(res); - }); - } - }); - }, - ); - }); - } - 3 => { - ui.horizontal(|ui| { - View::small_loading_spinner(ui); - ui.add_space(8.0); - ui.label( - RichText::new(t!("goblin.settings.rotating_key")) - .font(FontId::new(13.0, fonts::regular())) - .color(t.surface_text_dim), - ); - }); - ui.ctx().request_repaint(); - } - 4 => { - ui.label( - RichText::new(t!("goblin.settings.key_rotated")) - .font(FontId::new(15.0, fonts::semibold())) - .color(t.pos), - ); - ui.add_space(4.0); - let npub = &rotate.new_npub; - let short = if npub.len() > 18 { - format!("{}…{}", &npub[..12], &npub[npub.len() - 6..]) - } else { - npub.clone() - }; - ui.label( - RichText::new(t!("goblin.settings.new_npub", npub => short)) - .font(FontId::new(13.0, fonts::mono())) - .color(t.surface_text_dim), - ); - ui.add_space(6.0); - ui.label( - RichText::new(t!("goblin.settings.backup_new_key")) - .font(FontId::new(13.0, fonts::semibold())) - .color(t.neg), - ); - ui.add_space(10.0); - if w::big_action_on_card(ui, &t!("goblin.settings.copy_new_nsec")).clicked() { - if let Some(nsec) = wallet.nostr_service().and_then(|s| s.nsec()) { - // Secret: auto-clears from the clipboard after a delay - // (compare-then-clear) so it does not linger there. - cb.copy_secret_to_buffer(nsec); - cb.vibrate_copy(); - } - } - ui.add_space(8.0); - if w::big_action(ui, &t!("goblin.settings.done"), false).clicked() { - close = true; - } - } - _ => { - ui.label( - RichText::new(t!("goblin.settings.rotation_failed")) - .font(FontId::new(15.0, fonts::semibold())) - .color(t.neg), - ); - ui.add_space(4.0); - ui.label( - RichText::new(&rotate.error) - .font(FontId::new(13.0, fonts::regular())) - .color(t.surface_text_dim), - ); - ui.add_space(10.0); - if w::big_action_on_card(ui, &t!("goblin.settings.close")).clicked() { - close = true; - } - } - } - }); - if close { - self.rotate = None; - } - } - /// Inline "back up identity to a file" flow: ask for the wallet password, /// seal the identity, and write a GOBLIN-*.backup file via the native picker. fn backup_ui(&mut self, ui: &mut egui::Ui, wallet: &Wallet, cb: &dyn PlatformCallbacks) { diff --git a/src/wallet/wallet.rs b/src/wallet/wallet.rs index 60d38013..0d20af6d 100644 --- a/src/wallet/wallet.rs +++ b/src/wallet/wallet.rs @@ -604,74 +604,6 @@ impl Wallet { .map_err(|e| format!("nsec encode failed: {e}")) } - pub fn rotate_nostr_identity(&self, password: String) -> Result { - let svc = self - .nostr_service() - .ok_or_else(|| "nostr is not running".to_string())?; - // Snapshot the old identity and prove the password by unlocking it - // (NIP-49 decryption fails on a wrong password). - let old = svc.identity.read().clone(); - let old_keys = old - .unlock(&password) - .map_err(|_| "Wrong password".to_string())?; - - // Generate the replacement identity. - let (mut new_identity, _new_keys) = NostrIdentity::create_random(&password) - .map_err(|e| format!("key generation failed: {e}"))?; - - // Release the username first (the server also deletes its avatar); - // abort the rotation if that fails so the user never ends up with a - // burned key still welded to a public name. After rotation the name - // is up for grabs — by the new key or anyone else. - if let Some(nip05) = old.nip05.clone() { - let name = nip05.split('@').next().unwrap_or_default().to_string(); - let server = { svc.config.read().nip05_server() }; - let rt = tokio::runtime::Builder::new_current_thread() - .enable_all() - .build() - .map_err(|e| e.to_string())?; - rt.block_on(async { crate::nostr::nip05::unregister(&server, &name, &old_keys).await }) - .map_err(|e| format!("Couldn't release @{name}: {e} — rotation cancelled"))?; - } - new_identity.prev_npubs = { - let mut v = old.prev_npubs.clone(); - v.push(old.npub.clone()); - v - }; - - // Persist, then swap the running service for one bound to the new key. - let config = self.get_config(); - let nostr_dir = config.get_nostr_path(); - new_identity - .save(&nostr_dir) - .map_err(|e| format!("identity save failed: {e}"))?; - svc.stop(); - for _ in 0..100 { - if !svc.is_running() { - break; - } - thread::sleep(Duration::from_millis(100)); - } - let wallet_dir = PathBuf::from(config.get_data_path()); - let nostr_config = NostrConfig::load(wallet_dir); - let store = NostrStore::new(config.get_nostr_db_path()); - let new_npub = new_identity.npub.clone(); - // Rebuild the service holding ALL held identities (the primary entry now - // resolves to the new identity), with the new one active. - let (recv, _) = self - .unlock_all_identities(&nostr_dir, &password) - .ok_or_else(|| "identity unlock failed".to_string())?; - let active_hex = new_identity.pubkey_hex().unwrap_or_default(); - let new_svc = NostrService::new(recv, &active_hex, nostr_config, store, nostr_dir); - { - let mut w_nostr = self.nostr.write(); - *w_nostr = Some(new_svc.clone()); - } - new_svc.start(self.clone()); - info!("nostr: identity rotated to {}", new_npub); - Ok(new_npub) - } - /// Replace the nostr identity with an imported key — either a bare nsec /// or an exported identity-backup JSON (which restores the username and /// rotation history too). The current identity is overwritten; its npub @@ -791,9 +723,9 @@ impl Wallet { // identities and present a different one at will. Exactly one is ACTIVE: it // drives the single live gift-wrap subscription and all display, and every // identity redeems into the SAME shared grin balance. Only the active nsec is - // ever decrypted into memory; the rest rest as ncryptsec on disk. Switching is - // mechanically identical to `rotate_nostr_identity` (stop the service, rebuild - // it on the target key, restart), plus a per-identity catch-up so payments that + // ever decrypted into memory; the rest rest as ncryptsec on disk. Switching + // stops the service and rebuilds it on the target key, then restarts, plus a + // per-identity catch-up so payments that // arrived while an identity was dormant are redeemed on switch-in. /// The held identities for the identity switcher (no secrets). Empty if nostr