Skip to content

Commit

Permalink
Implement Resume for encrypted backup
Browse files Browse the repository at this point in the history
  • Loading branch information
afilini committed Sep 30, 2024
1 parent 568685a commit ce76812
Show file tree
Hide file tree
Showing 9 changed files with 111 additions and 31 deletions.
8 changes: 6 additions & 2 deletions emulator/gui.fl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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}
Expand All @@ -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());}
}
}
Expand Down
14 changes: 14 additions & 0 deletions emulator/src/gui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 |_| {
Expand Down
2 changes: 1 addition & 1 deletion firmware/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion firmware/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "firmware"
version = "0.3.1"
version = "0.3.3"
edition = "2021"
license = "GPL-3.0-or-later"

Expand Down
86 changes: 64 additions & 22 deletions firmware/src/handlers/init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -542,27 +542,52 @@ pub async fn display_mnemonic(

pub async fn display_encryption_key(
config: UnverifiedConfig,
encryption_key: &str,
mut events: impl Stream<Item = Event> + Unpin,
peripherals: &mut HandlerPeripherals,
) -> Result<CurrentState, Error> {
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::<alloc::vec::Vec<_>>().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()?;
Expand All @@ -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;
Expand Down Expand Up @@ -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(),
Expand All @@ -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,
}
}

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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,
}
}
4 changes: 2 additions & 2 deletions gui/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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...",
))
Expand Down
2 changes: 2 additions & 0 deletions model/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,8 @@ pub struct UnverifiedConfig {
pub descriptor: WalletDescriptor,
#[cbor(n(4))]
pub page: usize,
#[cbor(n(5))]
pub encryption_key: Option<String>,
}

#[derive(Debug, Clone, Encode, Decode)]
Expand Down
2 changes: 2 additions & 0 deletions sdk/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ required-features = ["cli"]
[[bin]]
name = "pcsc"
required-features = ["cli-pcsc"]
[[bin]]
name = "decrypt"


[[bin]]
Expand Down
22 changes: 19 additions & 3 deletions sdk/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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())
}

Expand Down Expand Up @@ -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<Option<Vec<u8>>, 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<model::bitcoin::Address, SdkError> {
Expand Down

0 comments on commit ce76812

Please sign in to comment.