Skip to content

Commit

Permalink
WIP: encrypted backup
Browse files Browse the repository at this point in the history
  • Loading branch information
afilini committed Aug 17, 2024
1 parent 3db53fe commit 568685a
Show file tree
Hide file tree
Showing 13 changed files with 270 additions and 47 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

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

14 changes: 9 additions & 5 deletions emulator/gui.fl
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,20 @@ class Emulator {open
code0 {inner_pack_1.set_spacing(5);}
} {
Fl_Choice num_words {open
xywh {35 305 150 35} down_box BORDER_BOX textfont 4
xywh {35 305 100 35} down_box BORDER_BOX textfont 4
code0 {let num_words = num_words.with_id("num_words");}
} {}
Fl_Input generate_mnemonic_password {
xywh {35 430 180 35} textfont 4
xywh {35 430 160 35} textfont 4
code0 { let generate_mnemonic_password = generate_mnemonic_password.with_id("generate_mnemonic_password"); }
}
Fl_Choice backup_method {open
xywh {35 305 100 35} down_box BORDER_BOX textfont 4
code0 {let backup_method = backup_method.with_id("backup_method");}
} {}
Fl_Button generate_mnemonic_btn {
label GenerateMnemonic
xywh {285 305 176 35}
label GenerateSeed
xywh {285 305 136 35}
}
}
Fl_Pack inner_pack_4 {open
Expand All @@ -48,7 +52,7 @@ class Emulator {open
}
Fl_Button restore_mnemonic_btn {
label Restore
xywh {300 430 100 35}
xywh {300 430 95 35}
}
}
Fl_Pack inner_pack_7 {open
Expand Down
61 changes: 45 additions & 16 deletions emulator/src/gui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ use tokio::sync::mpsc;

use portal::{GenerateMnemonicWords, PortalSdk};

use model::FwUpdateHeader;
use model::{FwUpdateHeader, SeedBackupMethod};

#[derive(Debug, serde::Deserialize, serde::Serialize)]
pub enum EmulatorMessage {
Expand All @@ -59,6 +59,10 @@ pub fn init_gui(
emulator_gui.num_words.add_choice("24 Words");
emulator_gui.num_words.set_value(0);

emulator_gui.backup_method.add_choice("Display");
emulator_gui.backup_method.add_choice("EncryptedFile");
emulator_gui.backup_method.set_value(0);

// TODO: assert fb size is 511x255
emulator_gui.display.draw(move |i| {
let fb = fb_large.read().unwrap();
Expand Down Expand Up @@ -115,28 +119,53 @@ pub fn init_gui(
let password = app::widget_from_id::<Input>("generate_mnemonic_password")
.unwrap()
.value();
let backup = match app::widget_from_id::<Choice>("backup_method")
.unwrap()
.value()
{
0 => SeedBackupMethod::Display,
1 => SeedBackupMethod::EncryptedFile,
_ => unimplemented!(),
};
let sdk_cloned = sdk_cloned.clone();
let log_cloned = log_cloned.clone();
tokio::spawn(async move {
log_cloned
.send(format!(
"> GenerateMnemonic({:?}, {:?})",
num_words, password
"> GenerateMnemonic({:?}, {:?}, {:?})",
num_words, password, backup
))
.unwrap();
match sdk_cloned
.generate_mnemonic(
num_words,
model::bitcoin::Network::Signet,
if password.is_empty() {
None
} else {
Some(password)
},
)
.await
{
Ok(v) => log_cloned.send("< ".into()).unwrap(),

let result = match backup {
SeedBackupMethod::Display => sdk_cloned
.generate_mnemonic(
num_words,
model::bitcoin::Network::Signet,
if password.is_empty() {
None
} else {
Some(password)
},
)
.await
.map(|_| "Ok".to_string()),
SeedBackupMethod::EncryptedFile => sdk_cloned
.generate_mnemonic_encrypted_backup(
num_words,
model::bitcoin::Network::Signet,
if password.is_empty() {
None
} else {
Some(password)
},
)
.await
.map(|seed| format!("{:02X?}", seed)),
};

match result {
Ok(v) => log_cloned.send(format!("< {}", v)).unwrap(),
Err(e) => log::warn!("Generate mnemonic err: {:?}", e),
}
});
Expand Down
2 changes: 2 additions & 0 deletions emulator/src/tests/init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ async fn test_generate_mnemonic_12words(mut tester: Tester) -> Result<(), crate:
model::NumWordsMnemonic::Words12,
model::bitcoin::Network::Signet,
None,
None,
))
.await?;
tester.display_assertion("iVBORw0KGgoAAAANSUhEUgAAAIAAAABACAAAAAD3vSCjAAABbUlEQVR4nO2YURLDIAhE5f6H3nZSQTAaNbbNz/YjGW2AN4iyiaSHfwQgAAH2AHBljynnBCjPQJYiH7bvGDh85LG/yyIAlpOh0ZFjqYc8RloCgKxnoAQ2gGr+Z0tQB0bqAP0PQJrzNwDmMHSNWyA6v1oDWtZpkuCIEtfeVf+NXXBnM7IXEIAAN5sIAXrteOSoaIBw+tWaII7dKTlqx6NTsNEBxd9bGgEeeLwEcwDeeTV/aslLAKO1hBlohqXfmgPAZbHJdBcywE4GmupoLAukit/n6NfAWQuoShK/RNt6oL8LgkJpaYSJXfB9PTApy37YCzDln92QAAQgwPMAIMBpLCZ7nLRzQkTcFwmvhaybInbFbPG5JHN4BaAX5Slkmc6sBPUjOQ7yw8WiOL0GSI44AHzQ81t/D0Apkn4qKBb6B9J2BiBDAEgAyCmZysAegNnuAZjkRF2EMWIyZdgsQntDqZzyHGAvIAABCECA8HsBUJhXRmrXkc8AAAAASUVORK5CYII=", None).await?;
Expand Down Expand Up @@ -319,6 +320,7 @@ async fn test_reset_during_generate_mnemonic(mut tester: Tester) -> Result<(), c
model::NumWordsMnemonic::Words12,
model::bitcoin::Network::Signet,
None,
None,
))
.await?;
tester.display_assertion("iVBORw0KGgoAAAANSUhEUgAAAIAAAABACAAAAAD3vSCjAAABbUlEQVR4nO2YURLDIAhE5f6H3nZSQTAaNbbNz/YjGW2AN4iyiaSHfwQgAAH2AHBljynnBCjPQJYiH7bvGDh85LG/yyIAlpOh0ZFjqYc8RloCgKxnoAQ2gGr+Z0tQB0bqAP0PQJrzNwDmMHSNWyA6v1oDWtZpkuCIEtfeVf+NXXBnM7IXEIAAN5sIAXrteOSoaIBw+tWaII7dKTlqx6NTsNEBxd9bGgEeeLwEcwDeeTV/aslLAKO1hBlohqXfmgPAZbHJdBcywE4GmupoLAukit/n6NfAWQuoShK/RNt6oL8LgkJpaYSJXfB9PTApy37YCzDln92QAAQgwPMAIMBpLCZ7nLRzQkTcFwmvhaybInbFbPG5JHN4BaAX5Slkmc6sBPUjOQ7yw8WiOL0GSI44AHzQ81t/D0Apkn4qKBb6B9J2BiBDAEgAyCmZysAegNnuAZjkRF2EMWIyZdgsQntDqZzyHGAvIAABCECA8HsBUJhXRmrXkc8AAAAASUVORK5CYII=", None).await?;
Expand Down
4 changes: 2 additions & 2 deletions emulator/src/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ async fn run_script(
NfcAction::DisplayAddress(addr) => tokio::spawn(async move {
let _ = cloned_sdk.display_address(addr).await;
}),
NfcAction::GenerateMnemonic(num_words, network, pair_code) => {
NfcAction::GenerateMnemonic(num_words, network, pair_code, backup) => {
tokio::spawn(async move {
let num_words = match num_words {
model::NumWordsMnemonic::Words12 => {
Expand All @@ -107,7 +107,7 @@ async fn run_script(
}
};
let _ = cloned_sdk
.generate_mnemonic(num_words, network, pair_code)
.generate_mnemonic(num_words, network, pair_code, backup)
.await;
})
}
Expand Down
2 changes: 2 additions & 0 deletions emulator/src/utils/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

use core::fmt;

use model::SeedBackupMethod;
use serde::{Deserialize, Serialize};

use embedded_graphics::pixelcolor::Gray8;
Expand Down Expand Up @@ -61,6 +62,7 @@ pub enum NfcAction {
model::NumWordsMnemonic,
model::bitcoin::Network,
Option<String>,
Option<SeedBackupMethod>,
),
RestoreMnemonic(String, model::bitcoin::Network, Option<String>),
RequestDescriptors,
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.

82 changes: 76 additions & 6 deletions firmware/src/handlers/init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,11 @@ use core::str::FromStr;

use futures::prelude::*;

use rand::RngCore;
use rand::{RngCore, Rng};

use gui::{ConfirmPairCodePage, SingleLineTextPage};
use gui::{ConfirmEncryptionKeyPage, ConfirmPairCodePage, SingleLineTextPage};
use model::{
Entropy, ExtendedKey, InitializedConfig, MultisigKey, ScriptType, UnlockedConfig,
UnverifiedConfig, WalletDescriptor,
EncryptedBackupData, Entropy, ExtendedKey, InitializedConfig, MultisigKey, ScriptType, UnlockedConfig, UnverifiedConfig, WalletDescriptor
};

use bdk_wallet::bitcoin::bip32;
Expand All @@ -36,7 +35,7 @@ use bdk_wallet::miniscript;
use bdk_wallet::miniscript::descriptor::{DescriptorXKey, KeyMap, Wildcard};

use gui::{GeneratingMnemonicPage, LoadingPage, MnemonicPage, Page, WelcomePage};
use model::{Config, DeviceInfo};
use model::{Config, DeviceInfo, SeedBackupMethod};

use super::*;
use crate::config;
Expand Down Expand Up @@ -374,11 +373,13 @@ pub async fn handle_init(
num_words,
network,
password,
backup,
}) => {
break Ok(CurrentState::GenerateSeed {
num_words,
network,
password,
backup: backup.unwrap_or(SeedBackupMethod::Display)
});
}
Some(model::Request::SetMnemonic {
Expand Down Expand Up @@ -539,6 +540,70 @@ pub async fn display_mnemonic(
})
}

pub async fn display_encryption_key(
config: UnverifiedConfig,
mut events: impl Stream<Item = Event> + Unpin,
peripherals: &mut HandlerPeripherals,
) -> Result<CurrentState, Error> {
peripherals.tsc_enabled.enable();

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 encryption_key: String = (0..PASSWORD_LEN)
.map(|_| {
let idx = peripherals.rng.gen_range(0..CHARSET.len());
CHARSET[idx] as char
})
.collect();

let mut page = ConfirmEncryptionKeyPage::new(&encryption_key);
page.init_display(&mut peripherals.display)?;
page.draw_to(&mut peripherals.display)?;
peripherals.display.flush()?;

manage_confirmation_loop(&mut events, peripherals, &mut page).await?;

if let Some(pair_code) = &config.pair_code {
let mut page = ConfirmPairCodePage::new(pair_code);
page.init_display(&mut peripherals.display)?;
page.draw_to(&mut peripherals.display)?;
peripherals.display.flush()?;

manage_confirmation_loop(&mut events, peripherals, &mut page).await?;
}

let page = LoadingPage::new();
page.init_display(&mut peripherals.display)?;
page.draw_to(&mut peripherals.display)?;
peripherals.display.flush()?;

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_finished.recv().await.unwrap();

let network = config.network;
let (initialized, unlocked, xprv) = config.upgrade(salt);
config::write_config(&mut peripherals.flash, &Config::Initialized(initialized))?;

Ok(CurrentState::Idle {
wallet: Rc::new(make_wallet_from_xprv(xprv, network, unlocked)?),
})
}

async fn save_unverified_config(
unverified_config: UnverifiedConfig,
peripherals: &mut HandlerPeripherals,
Expand All @@ -557,6 +622,7 @@ pub async fn handle_generate_seed(
num_words: model::NumWordsMnemonic,
network: Network,
password: Option<String>,
backup: SeedBackupMethod,
events: impl Stream<Item = Event> + Unpin,
peripherals: &mut HandlerPeripherals,
) -> Result<CurrentState, Error> {
Expand Down Expand Up @@ -584,7 +650,11 @@ pub async fn handle_generate_seed(
page: 0,
};
let unverified_config = save_unverified_config(unverified_config, peripherals).await?;
display_mnemonic(unverified_config, events, peripherals).await

match backup {
SeedBackupMethod::Display => display_mnemonic(unverified_config, events, peripherals).await,
SeedBackupMethod::EncryptedFile => display_encryption_key(unverified_config, events, peripherals).await,
}
}

pub async fn handle_import_seed(
Expand Down
12 changes: 4 additions & 8 deletions firmware/src/handlers/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ use futures::prelude::*;

use gui::{ConfirmBarPage, ErrorPage, MainContent, Page};
use model::bitcoin::bip32;
use model::{FwUpdateHeader, NumWordsMnemonic, Reply};
use model::{FwUpdateHeader, NumWordsMnemonic, Reply, SeedBackupMethod};

use crate::{checkpoint, hw, Error};

Expand Down Expand Up @@ -78,6 +78,7 @@ pub enum CurrentState {
num_words: NumWordsMnemonic,
network: bdk_wallet::bitcoin::Network,
password: Option<String>,
backup: SeedBackupMethod,
},
/// Importing seed
ImportSeed {
Expand Down Expand Up @@ -245,20 +246,15 @@ pub async fn dispatch_handler<'a>(
num_words,
network,
password,
backup,
} => {
peripherals
.nfc
.send(model::Reply::DelayedReply)
.await
.unwrap();

Box::pin(init::handle_generate_seed(
num_words,
network,
password,
events,
peripherals,
)) as Pin<Box<dyn Future<Output = Result<CurrentState, Error>>>>
Box::pin(init::handle_generate_seed(num_words, network, password, backup, events, peripherals)) as Pin<Box<dyn Future<Output = Result<CurrentState, Error>>>>
}
CurrentState::ImportSeed {
mnemonic,
Expand Down
16 changes: 16 additions & 0 deletions gui/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -620,6 +620,22 @@ impl<'s, 'l> MainContent for TwoLinesText<'s, 'l> {
}
}

pub struct ConfirmEncryptionKeyPage<'s>(ConfirmBarPage<'static, TwoLinesText<'static, 's>>);
impl_wrapper_page!(
ConfirmEncryptionKeyPage<'s>,
ConfirmBarPage<'static, TwoLinesText<'static, 's>>
);
impl<'s> ConfirmEncryptionKeyPage<'s> {
pub fn new(pair_code: &'s str) -> Self {
ConfirmEncryptionKeyPage(ConfirmBarPage::new_default_bar(
100,
TwoLinesText::new("Backup Key", pair_code),
"HOLD BTN TO CONFIRM",
"KEEP HOLDING...",
))
}
}

pub struct ConfirmPairCodePage<'s>(ConfirmBarPage<'static, TwoLinesText<'static, 's>>);
impl_wrapper_page!(
ConfirmPairCodePage<'s>,
Expand Down
2 changes: 1 addition & 1 deletion model/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "model"
version = "0.1.0"
version = "0.3.0"
edition = "2021"
license = "GPL-3.0-or-later"

Expand Down
Loading

0 comments on commit 568685a

Please sign in to comment.