diff --git a/emulator/gui.fl b/emulator/gui.fl index d4d1500..bab64f9 100644 --- a/emulator/gui.fl +++ b/emulator/gui.fl @@ -8,7 +8,7 @@ class Emulator {open } { Fl_Window window { label Emulator open - xywh {1162 204 565 1085} type Double size_range {561 1020 561 0} visible + xywh {1162 204 565 1115} type Double size_range {561 1020 561 0} visible } { Fl_Box display { xywh {25 25 511 255} labeltype NO_LABEL @@ -141,6 +141,10 @@ class Emulator {open xywh {300 430 150 35} } } + Fl_Button wipe_btn { + label Wipe + xywh {315 375 511 35} + } Fl_Button resume_btn { label Resume xywh {315 375 511 35} @@ -151,7 +155,7 @@ class Emulator {open } } Fl_Text_Display console {selected - xywh {25 790 511 270} labeltype NO_LABEL textfont 4 + xywh {25 825 511 270} labeltype NO_LABEL textfont 4 code0 {console.set_buffer(text::TextBuffer::default());} } } diff --git a/emulator/src/gui.rs b/emulator/src/gui.rs index 537a1a0..6638b5a 100644 --- a/emulator/src/gui.rs +++ b/emulator/src/gui.rs @@ -203,6 +203,20 @@ pub fn init_gui( sender.send(EmulatorMessage::Reset).unwrap(); }); + let sdk_cloned = Arc::clone(&sdk); + let log_cloned = log.clone(); + emulator_gui.wipe_btn.set_callback(move |_| { + let sdk_cloned = sdk_cloned.clone(); + let log_cloned = log_cloned.clone(); + tokio::spawn(async move { + log_cloned.send("> Wipe".into()).unwrap(); + match sdk_cloned.debug_wipe_device().await { + Ok(v) => log_cloned.send(format!("< {:?}", v)).unwrap(), + Err(e) => log::warn!("Wipe err: {:?}", e), + } + }); + }); + let sdk_cloned = Arc::clone(&sdk); let log_cloned = log.clone(); emulator_gui.resume_btn.set_callback(move |_| { diff --git a/firmware/Cargo.lock b/firmware/Cargo.lock index 45342bf..9c22c60 100644 --- a/firmware/Cargo.lock +++ b/firmware/Cargo.lock @@ -451,7 +451,7 @@ version = "0.1.0" [[package]] name = "firmware" -version = "0.3.1" +version = "0.3.3" dependencies = [ "bdk_wallet", "bitcoin", diff --git a/firmware/Cargo.toml b/firmware/Cargo.toml index 11b9066..ea45bcf 100644 --- a/firmware/Cargo.toml +++ b/firmware/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "firmware" -version = "0.3.1" +version = "0.3.3" edition = "2021" license = "GPL-3.0-or-later" diff --git a/firmware/src/handlers/init.rs b/firmware/src/handlers/init.rs index 7e08083..b6ddb62 100644 --- a/firmware/src/handlers/init.rs +++ b/firmware/src/handlers/init.rs @@ -542,27 +542,52 @@ pub async fn display_mnemonic( pub async fn display_encryption_key( config: UnverifiedConfig, + encryption_key: &str, mut events: impl Stream + Unpin, peripherals: &mut HandlerPeripherals, ) -> Result { peripherals.tsc_enabled.enable(); + let page = LoadingPage::new(); + page.init_display(&mut peripherals.display)?; + page.draw_to(&mut peripherals.display)?; + peripherals.display.flush()?; + let mnemonic = Mnemonic::from_entropy(&config.entropy.bytes).map_err(map_err_config)?; let mnemonic = mnemonic.word_iter().collect::>().join(" "); - const CHARSET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ\ - abcdefghijklmnopqrstuvwxyz\ - 0123456789)(*%$#!"; - const PASSWORD_LEN: usize = 14; + let backup = EncryptedBackupData { + mnemonic, + network: config.network, + }; + let backup = backup.encrypt(&encryption_key); + + // Send the encrypted backup immediately and make sure the SDK acknowledges it by sending "Resume" + peripherals.nfc.send(model::Reply::EncryptedSeed(backup.into())).await.unwrap(); - let encryption_key: String = (0..PASSWORD_LEN) - .map(|_| { - let idx = peripherals.rng.gen_range(0..CHARSET.len()); - CHARSET[idx] as char - }) - .collect(); + loop { + match events.next().await { + Some(Event::Request(model::Request::Resume)) => { + peripherals + .nfc + .send(model::Reply::DelayedReply) + .await + .unwrap(); + peripherals.nfc_finished.recv().await.unwrap(); + break; + } + + Some(Event::Request(_)) => { + peripherals.nfc.send(model::Reply::UnexpectedMessage).await.unwrap(); + peripherals.nfc_finished.recv().await.unwrap(); + continue; + } + Some(_) => continue, + _ => unreachable!(), + } + } - let mut page = ConfirmEncryptionKeyPage::new(&encryption_key); + let mut page = ConfirmEncryptionKeyPage::new(encryption_key); page.init_display(&mut peripherals.display)?; page.draw_to(&mut peripherals.display)?; peripherals.display.flush()?; @@ -586,13 +611,7 @@ pub async fn display_encryption_key( let mut salt = [0; 8]; peripherals.rng.fill_bytes(&mut salt); - let backup = EncryptedBackupData { - mnemonic, - network: config.network, - }; - let backup = backup.encrypt(&encryption_key); - - peripherals.nfc.send(model::Reply::EncryptedSeed(backup.into())).await.unwrap(); + peripherals.nfc.send(model::Reply::Ok).await.unwrap(); peripherals.nfc_finished.recv().await.unwrap(); let network = config.network; @@ -640,6 +659,24 @@ pub async fn handle_generate_seed( let descriptor = WalletDescriptor::make_bip84(network); + let encryption_key = match backup { + SeedBackupMethod::Display => None, + SeedBackupMethod::EncryptedFile => { + const CHARSET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ\ + abcdefghijklmnopqrstuvwxyz\ + 0123456789)(*%$#!"; + const PASSWORD_LEN: usize = 14; + + let encryption_key: String = (0..PASSWORD_LEN) + .map(|_| { + let idx = peripherals.rng.gen_range(0..CHARSET.len()); + CHARSET[idx] as char + }) + .collect(); + Some(encryption_key) + } + }; + let unverified_config = UnverifiedConfig { entropy: Entropy { bytes: alloc::vec::Vec::from(entropy).into(), @@ -648,12 +685,13 @@ pub async fn handle_generate_seed( pair_code: password, descriptor, page: 0, + encryption_key, }; let unverified_config = save_unverified_config(unverified_config, peripherals).await?; - match backup { - SeedBackupMethod::Display => display_mnemonic(unverified_config, events, peripherals).await, - SeedBackupMethod::EncryptedFile => display_encryption_key(unverified_config, events, peripherals).await, + match unverified_config.encryption_key.clone() { + None => display_mnemonic(unverified_config, events, peripherals).await, + Some(key) => display_encryption_key(unverified_config, &key, events, peripherals).await, } } @@ -683,6 +721,7 @@ pub async fn handle_import_seed( pair_code: password, descriptor, page: 0, + encryption_key: None, }; let unverified_config = save_unverified_config(unverified_config, peripherals).await?; display_mnemonic(unverified_config, events, peripherals).await @@ -739,5 +778,8 @@ pub async fn handle_unverified_config( } } - display_mnemonic(config, events, peripherals).await + match config.encryption_key.clone() { + None => display_mnemonic(config, events, peripherals).await, + Some(key) => display_encryption_key(config, &key, events, peripherals).await, + } } diff --git a/gui/src/lib.rs b/gui/src/lib.rs index a762b39..f9fcbd1 100644 --- a/gui/src/lib.rs +++ b/gui/src/lib.rs @@ -626,10 +626,10 @@ impl_wrapper_page!( ConfirmBarPage<'static, TwoLinesText<'static, 's>> ); impl<'s> ConfirmEncryptionKeyPage<'s> { - pub fn new(pair_code: &'s str) -> Self { + pub fn new(key: &'s str) -> Self { ConfirmEncryptionKeyPage(ConfirmBarPage::new_default_bar( 100, - TwoLinesText::new("Backup Key", pair_code), + TwoLinesText::new("Backup Key", key), "HOLD BTN TO CONFIRM", "KEEP HOLDING...", )) diff --git a/model/src/lib.rs b/model/src/lib.rs index 74ca5ab..494d2a3 100644 --- a/model/src/lib.rs +++ b/model/src/lib.rs @@ -310,6 +310,8 @@ pub struct UnverifiedConfig { pub descriptor: WalletDescriptor, #[cbor(n(4))] pub page: usize, + #[cbor(n(5))] + pub encryption_key: Option, } #[derive(Debug, Clone, Encode, Decode)] diff --git a/sdk/Cargo.toml b/sdk/Cargo.toml index 9985713..67ea527 100644 --- a/sdk/Cargo.toml +++ b/sdk/Cargo.toml @@ -42,6 +42,8 @@ required-features = ["cli"] [[bin]] name = "pcsc" required-features = ["cli-pcsc"] +[[bin]] +name = "decrypt" [[bin]] diff --git a/sdk/src/lib.rs b/sdk/src/lib.rs index e8c12a1..029f636 100644 --- a/sdk/src/lib.rs +++ b/sdk/src/lib.rs @@ -240,7 +240,12 @@ impl PortalSdk { GenerateMnemonicWords::Words24 => NumWordsMnemonic::Words24, }; + // Expect the backup immediately let encrypted_backup = send_with_retry!(self.requests, Request::GenerateMnemonic { num_words, network, password: password.clone(), backup: Some(SeedBackupMethod::EncryptedFile) }, Ok(Reply::EncryptedSeed(seed)) => break Ok(seed))?; + + // Acknowledge we received it + send_with_retry!(self.requests, Request::Resume, Ok(Reply::Ok) => break Ok(()))?; + Ok(encrypted_backup.into()) } @@ -274,9 +279,20 @@ impl PortalSdk { Ok(()) } - pub async fn resume(&self) -> Result<(), SdkError> { - send_with_retry!(self.requests, Request::Resume, Ok(Reply::Ok) => break Ok(()))?; - Ok(()) + pub async fn resume(&self) -> Result>, SdkError> { + let encrypted_backup = send_with_retry!( + self.requests, + Request::Resume, + Ok(Reply::EncryptedSeed(seed)) => break Ok(Some(seed)), + Ok(Reply::Ok) => break Ok(None) + )?; + + // Acknowledge reception + if encrypted_backup.is_some() { + send_with_retry!(self.requests, Request::Resume, Ok(Reply::Ok) => break Ok(()))?; + } + + Ok(encrypted_backup.map(Into::into)) } pub async fn display_address(&self, index: u32) -> Result {