Merge #487: Check hot signer usage using descriptor
307d12d980b618a47f5a929046d0643e688cd1ff Check hot signer usage using descriptor (edouard) Pull request description: It is a refont of the installer flow to use less optional components in the installer context. Hot signer is stored a the install step if its fingerprint is present in the descriptor string close #397 ACKs for top commit: edouardparis: Self-ACK 307d12d980b618a47f5a929046d0643e688cd1ff Tree-SHA512: 018ddb17ccc780f808b804b3ded273d81032d0ea3275ce5fd4cc7418c996c53f47be842895a25583b404675a041e0f1bb7b5e861a2570f66f1c14d03f966f266
This commit is contained in:
commit
7d4ac9d56a
@ -30,8 +30,10 @@ pub struct Context {
|
|||||||
Option<[u8; 32]>,
|
Option<[u8; 32]>,
|
||||||
)>,
|
)>,
|
||||||
pub data_dir: PathBuf,
|
pub data_dir: PathBuf,
|
||||||
pub signer: Option<Arc<Signer>>,
|
|
||||||
pub hw_is_used: bool,
|
pub hw_is_used: bool,
|
||||||
|
// In case a user entered a mnemonic,
|
||||||
|
// we dont want to override the generated signer with it.
|
||||||
|
pub recovered_signer: Option<Arc<Signer>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Context {
|
impl Context {
|
||||||
@ -46,8 +48,8 @@ impl Context {
|
|||||||
bitcoind_config: None,
|
bitcoind_config: None,
|
||||||
descriptor: None,
|
descriptor: None,
|
||||||
data_dir,
|
data_dir,
|
||||||
signer: None,
|
|
||||||
hw_is_used: false,
|
hw_is_used: false,
|
||||||
|
recovered_signer: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -10,10 +10,15 @@ use liana_ui::widget::Element;
|
|||||||
use tracing::{error, info, warn};
|
use tracing::{error, info, warn};
|
||||||
|
|
||||||
use context::Context;
|
use context::Context;
|
||||||
|
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
use crate::app::{config as gui_config, settings as gui_settings};
|
use crate::{
|
||||||
|
app::{config as gui_config, settings as gui_settings},
|
||||||
|
signer::Signer,
|
||||||
|
};
|
||||||
|
|
||||||
pub use message::Message;
|
pub use message::Message;
|
||||||
use step::{
|
use step::{
|
||||||
@ -24,6 +29,7 @@ use step::{
|
|||||||
pub struct Installer {
|
pub struct Installer {
|
||||||
current: usize,
|
current: usize,
|
||||||
steps: Vec<Box<dyn Step>>,
|
steps: Vec<Box<dyn Step>>,
|
||||||
|
signer: Arc<Mutex<Signer>>,
|
||||||
|
|
||||||
/// Context is data passed through each step.
|
/// Context is data passed through each step.
|
||||||
context: Context,
|
context: Context,
|
||||||
@ -55,6 +61,7 @@ impl Installer {
|
|||||||
current: 0,
|
current: 0,
|
||||||
steps: vec![Welcome::default().into()],
|
steps: vec![Welcome::default().into()],
|
||||||
context: Context::new(network, destination_path),
|
context: Context::new(network, destination_path),
|
||||||
|
signer: Arc::new(Mutex::new(Signer::generate(network).unwrap())),
|
||||||
},
|
},
|
||||||
Command::none(),
|
Command::none(),
|
||||||
)
|
)
|
||||||
@ -98,29 +105,30 @@ impl Installer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn update(&mut self, message: Message) -> Command<Message> {
|
pub fn update(&mut self, message: Message) -> Command<Message> {
|
||||||
|
let hot_signer_fingerprint = self.signer.lock().unwrap().fingerprint();
|
||||||
match message {
|
match message {
|
||||||
Message::CreateWallet => {
|
Message::CreateWallet => {
|
||||||
self.steps = vec![
|
self.steps = vec![
|
||||||
Welcome::default().into(),
|
Welcome::default().into(),
|
||||||
DefineDescriptor::new().into(),
|
DefineDescriptor::new(self.signer.clone()).into(),
|
||||||
BackupMnemonic::default().into(),
|
BackupMnemonic::new(self.signer.clone()).into(),
|
||||||
BackupDescriptor::default().into(),
|
BackupDescriptor::default().into(),
|
||||||
RegisterDescriptor::default().into(),
|
RegisterDescriptor::default().into(),
|
||||||
DefineBitcoind::new().into(),
|
DefineBitcoind::new().into(),
|
||||||
Final::new().into(),
|
Final::new(hot_signer_fingerprint).into(),
|
||||||
];
|
];
|
||||||
self.next()
|
self.next()
|
||||||
}
|
}
|
||||||
Message::ParticipateWallet => {
|
Message::ParticipateWallet => {
|
||||||
self.steps = vec![
|
self.steps = vec![
|
||||||
Welcome::default().into(),
|
Welcome::default().into(),
|
||||||
ParticipateXpub::new().into(),
|
ParticipateXpub::new(self.signer.clone()).into(),
|
||||||
ImportDescriptor::new(false).into(),
|
ImportDescriptor::new(false).into(),
|
||||||
BackupMnemonic::default().into(),
|
BackupMnemonic::new(self.signer.clone()).into(),
|
||||||
BackupDescriptor::default().into(),
|
BackupDescriptor::default().into(),
|
||||||
RegisterDescriptor::default().into(),
|
RegisterDescriptor::default().into(),
|
||||||
DefineBitcoind::new().into(),
|
DefineBitcoind::new().into(),
|
||||||
Final::new().into(),
|
Final::new(hot_signer_fingerprint).into(),
|
||||||
];
|
];
|
||||||
self.next()
|
self.next()
|
||||||
}
|
}
|
||||||
@ -131,7 +139,7 @@ impl Installer {
|
|||||||
RecoverMnemonic::default().into(),
|
RecoverMnemonic::default().into(),
|
||||||
RegisterDescriptor::default().into(),
|
RegisterDescriptor::default().into(),
|
||||||
DefineBitcoind::new().into(),
|
DefineBitcoind::new().into(),
|
||||||
Final::new().into(),
|
Final::new(hot_signer_fingerprint).into(),
|
||||||
];
|
];
|
||||||
self.next()
|
self.next()
|
||||||
}
|
}
|
||||||
@ -146,7 +154,10 @@ impl Installer {
|
|||||||
.get_mut(self.current)
|
.get_mut(self.current)
|
||||||
.expect("There is always a step")
|
.expect("There is always a step")
|
||||||
.update(message);
|
.update(message);
|
||||||
Command::perform(install(self.context.clone()), Message::Installed)
|
Command::perform(
|
||||||
|
install(self.context.clone(), self.signer.clone()),
|
||||||
|
Message::Installed,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
Message::Installed(Err(e)) => {
|
Message::Installed(Err(e)) => {
|
||||||
let mut data_dir = self.context.data_dir.clone();
|
let mut data_dir = self.context.data_dir.clone();
|
||||||
@ -219,7 +230,7 @@ pub fn daemon_check(cfg: liana::config::Config) -> Result<(), Error> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn install(ctx: Context) -> Result<PathBuf, Error> {
|
pub async fn install(ctx: Context, signer: Arc<Mutex<Signer>>) -> Result<PathBuf, Error> {
|
||||||
let mut cfg: liana::config::Config = ctx.extract_daemon_config();
|
let mut cfg: liana::config::Config = ctx.extract_daemon_config();
|
||||||
let data_dir = cfg.data_dir.unwrap();
|
let data_dir = cfg.data_dir.unwrap();
|
||||||
|
|
||||||
@ -248,8 +259,14 @@ pub async fn install(ctx: Context) -> Result<PathBuf, Error> {
|
|||||||
|
|
||||||
info!("Daemon configuration file created");
|
info!("Daemon configuration file created");
|
||||||
|
|
||||||
if let Some(signer) = &ctx.signer {
|
if cfg
|
||||||
|
.main_descriptor
|
||||||
|
.to_string()
|
||||||
|
.contains(&signer.lock().unwrap().fingerprint().to_string())
|
||||||
|
{
|
||||||
signer
|
signer
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
.store(
|
.store(
|
||||||
&cfg.data_dir().expect("Already checked"),
|
&cfg.data_dir().expect("Already checked"),
|
||||||
cfg.bitcoin_config.network,
|
cfg.bitcoin_config.network,
|
||||||
@ -259,6 +276,17 @@ pub async fn install(ctx: Context) -> Result<PathBuf, Error> {
|
|||||||
info!("Hot signer mnemonic stored");
|
info!("Hot signer mnemonic stored");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(signer) = &ctx.recovered_signer {
|
||||||
|
signer
|
||||||
|
.store(
|
||||||
|
&cfg.data_dir().expect("Already checked"),
|
||||||
|
cfg.bitcoin_config.network,
|
||||||
|
)
|
||||||
|
.map_err(|e| Error::Unexpected(format!("Failed to store mnemonic: {}", e)))?;
|
||||||
|
|
||||||
|
info!("Recovered signer mnemonic stored");
|
||||||
|
}
|
||||||
|
|
||||||
// create liana GUI configuration file
|
// create liana GUI configuration file
|
||||||
let gui_config_path = create_and_write_file(
|
let gui_config_path = create_and_write_file(
|
||||||
network_datadir_path.clone(),
|
network_datadir_path.clone(),
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
use std::collections::{BTreeMap, HashMap, HashSet};
|
use std::collections::{BTreeMap, HashMap, HashSet};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::sync::Arc;
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
use iced::Command;
|
use iced::Command;
|
||||||
use liana::{
|
use liana::{
|
||||||
@ -97,13 +97,13 @@ pub struct DefineDescriptor {
|
|||||||
recovery_paths: Vec<RecoveryPath>,
|
recovery_paths: Vec<RecoveryPath>,
|
||||||
|
|
||||||
modal: Option<Box<dyn DescriptorEditModal>>,
|
modal: Option<Box<dyn DescriptorEditModal>>,
|
||||||
signer: Arc<Signer>,
|
signer: Arc<Mutex<Signer>>,
|
||||||
|
|
||||||
error: Option<String>,
|
error: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DefineDescriptor {
|
impl DefineDescriptor {
|
||||||
pub fn new() -> Self {
|
pub fn new(signer: Arc<Mutex<Signer>>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
network: Network::Bitcoin,
|
network: Network::Bitcoin,
|
||||||
data_dir: None,
|
data_dir: None,
|
||||||
@ -112,7 +112,7 @@ impl DefineDescriptor {
|
|||||||
spending_threshold: 1,
|
spending_threshold: 1,
|
||||||
recovery_paths: vec![RecoveryPath::new()],
|
recovery_paths: vec![RecoveryPath::new()],
|
||||||
modal: None,
|
modal: None,
|
||||||
signer: Arc::new(Signer::generate(Network::Bitcoin).unwrap()),
|
signer,
|
||||||
error: None,
|
error: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -125,9 +125,7 @@ impl DefineDescriptor {
|
|||||||
|
|
||||||
fn set_network(&mut self, network: Network) {
|
fn set_network(&mut self, network: Network) {
|
||||||
self.network = network;
|
self.network = network;
|
||||||
if let Some(signer) = Arc::get_mut(&mut self.signer) {
|
self.signer.lock().unwrap().set_network(network);
|
||||||
signer.set_network(network);
|
|
||||||
}
|
|
||||||
if let Some(mut network_datadir) = self.data_dir.clone() {
|
if let Some(mut network_datadir) = self.data_dir.clone() {
|
||||||
network_datadir.push(self.network.to_string());
|
network_datadir.push(self.network.to_string());
|
||||||
self.network_valid = !network_datadir.exists();
|
self.network_valid = !network_datadir.exists();
|
||||||
@ -448,7 +446,6 @@ impl Step for DefineDescriptor {
|
|||||||
ctx.bitcoin_config.network = self.network;
|
ctx.bitcoin_config.network = self.network;
|
||||||
ctx.keys = Vec::new();
|
ctx.keys = Vec::new();
|
||||||
let mut hw_is_used = false;
|
let mut hw_is_used = false;
|
||||||
let mut signer_is_used = false;
|
|
||||||
let mut spending_keys: Vec<DescriptorPublicKey> = Vec::new();
|
let mut spending_keys: Vec<DescriptorPublicKey> = Vec::new();
|
||||||
for spending_key in self.spending_keys.iter().clone() {
|
for spending_key in self.spending_keys.iter().clone() {
|
||||||
if let Some(DescriptorPublicKey::XPub(xpub)) = spending_key.key.as_ref() {
|
if let Some(DescriptorPublicKey::XPub(xpub)) = spending_key.key.as_ref() {
|
||||||
@ -457,9 +454,6 @@ impl Step for DefineDescriptor {
|
|||||||
master_fingerprint,
|
master_fingerprint,
|
||||||
name: spending_key.name.clone(),
|
name: spending_key.name.clone(),
|
||||||
});
|
});
|
||||||
if master_fingerprint == self.signer.fingerprint() {
|
|
||||||
signer_is_used = true;
|
|
||||||
}
|
|
||||||
if spending_key.device_kind.is_some() {
|
if spending_key.device_kind.is_some() {
|
||||||
hw_is_used = true;
|
hw_is_used = true;
|
||||||
}
|
}
|
||||||
@ -489,9 +483,6 @@ impl Step for DefineDescriptor {
|
|||||||
master_fingerprint,
|
master_fingerprint,
|
||||||
name: recovery_key.name.clone(),
|
name: recovery_key.name.clone(),
|
||||||
});
|
});
|
||||||
if master_fingerprint == self.signer.fingerprint() {
|
|
||||||
signer_is_used = true;
|
|
||||||
}
|
|
||||||
if recovery_key.device_kind.is_some() {
|
if recovery_key.device_kind.is_some() {
|
||||||
hw_is_used = true;
|
hw_is_used = true;
|
||||||
}
|
}
|
||||||
@ -539,9 +530,6 @@ impl Step for DefineDescriptor {
|
|||||||
|
|
||||||
ctx.descriptor = Some(LianaDescriptor::new(policy));
|
ctx.descriptor = Some(LianaDescriptor::new(policy));
|
||||||
ctx.hw_is_used = hw_is_used;
|
ctx.hw_is_used = hw_is_used;
|
||||||
if signer_is_used {
|
|
||||||
ctx.signer = Some(self.signer.clone());
|
|
||||||
}
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -650,12 +638,6 @@ fn check_key_network(key: &DescriptorPublicKey, network: Network) -> bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for DefineDescriptor {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::new()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<DefineDescriptor> for Box<dyn Step> {
|
impl From<DefineDescriptor> for Box<dyn Step> {
|
||||||
fn from(s: DefineDescriptor) -> Box<dyn Step> {
|
fn from(s: DefineDescriptor) -> Box<dyn Step> {
|
||||||
Box::new(s)
|
Box::new(s)
|
||||||
@ -738,7 +720,8 @@ pub struct EditXpubModal {
|
|||||||
edit_name: bool,
|
edit_name: bool,
|
||||||
|
|
||||||
hws: Vec<HardwareWallet>,
|
hws: Vec<HardwareWallet>,
|
||||||
hot_signer: Arc<Signer>,
|
hot_signer: Arc<Mutex<Signer>>,
|
||||||
|
hot_signer_fingerprint: Fingerprint,
|
||||||
chosen_signer: Option<(Fingerprint, Option<DeviceKind>)>,
|
chosen_signer: Option<(Fingerprint, Option<DeviceKind>)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -752,8 +735,9 @@ impl EditXpubModal {
|
|||||||
network: Network,
|
network: Network,
|
||||||
account_indexes: HashMap<Fingerprint, ChildNumber>,
|
account_indexes: HashMap<Fingerprint, ChildNumber>,
|
||||||
keys_aliases: HashMap<Fingerprint, String>,
|
keys_aliases: HashMap<Fingerprint, String>,
|
||||||
hot_signer: Arc<Signer>,
|
hot_signer: Arc<Mutex<Signer>>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
|
let hot_signer_fingerprint = hot_signer.lock().unwrap().fingerprint();
|
||||||
Self {
|
Self {
|
||||||
form_name: form::Value {
|
form_name: form::Value {
|
||||||
valid: true,
|
valid: true,
|
||||||
@ -773,6 +757,7 @@ impl EditXpubModal {
|
|||||||
network,
|
network,
|
||||||
edit_name: false,
|
edit_name: false,
|
||||||
chosen_signer: key.map(|k| (k.master_fingerprint(), None)),
|
chosen_signer: key.map(|k| (k.master_fingerprint(), None)),
|
||||||
|
hot_signer_fingerprint,
|
||||||
hot_signer,
|
hot_signer,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -838,7 +823,7 @@ impl DescriptorEditModal for EditXpubModal {
|
|||||||
return self.load();
|
return self.load();
|
||||||
}
|
}
|
||||||
Message::UseHotSigner => {
|
Message::UseHotSigner => {
|
||||||
let fingerprint = self.hot_signer.fingerprint();
|
let fingerprint = self.hot_signer.lock().unwrap().fingerprint();
|
||||||
self.chosen_signer = Some((fingerprint, None));
|
self.chosen_signer = Some((fingerprint, None));
|
||||||
self.form_xpub.valid = true;
|
self.form_xpub.valid = true;
|
||||||
if let Some(alias) = self.keys_aliases.get(&fingerprint) {
|
if let Some(alias) = self.keys_aliases.get(&fingerprint) {
|
||||||
@ -859,7 +844,10 @@ impl DescriptorEditModal for EditXpubModal {
|
|||||||
"[{}{}]{}",
|
"[{}{}]{}",
|
||||||
fingerprint,
|
fingerprint,
|
||||||
derivation_path.to_string().trim_start_matches('m'),
|
derivation_path.to_string().trim_start_matches('m'),
|
||||||
self.hot_signer.get_extended_pubkey(&derivation_path)
|
self.hot_signer
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.get_extended_pubkey(&derivation_path)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Message::DefineDescriptor(message::DefineDescriptor::KeyModal(msg)) => match msg {
|
Message::DefineDescriptor(message::DefineDescriptor::KeyModal(msg)) => match msg {
|
||||||
@ -957,8 +945,8 @@ impl DescriptorEditModal for EditXpubModal {
|
|||||||
self.error.as_ref(),
|
self.error.as_ref(),
|
||||||
self.processing,
|
self.processing,
|
||||||
self.chosen_signer.map(|s| s.0),
|
self.chosen_signer.map(|s| s.0),
|
||||||
&self.hot_signer,
|
&self.hot_signer_fingerprint,
|
||||||
self.keys_aliases.get(&self.hot_signer.fingerprint),
|
self.keys_aliases.get(&self.hot_signer_fingerprint),
|
||||||
&self.form_xpub,
|
&self.form_xpub,
|
||||||
&self.form_name,
|
&self.form_name,
|
||||||
self.edit_name,
|
self.edit_name,
|
||||||
@ -1073,13 +1061,13 @@ impl HardwareWalletXpubs {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct SignerXpubs {
|
pub struct SignerXpubs {
|
||||||
signer: Arc<Signer>,
|
signer: Arc<Mutex<Signer>>,
|
||||||
xpubs: Vec<String>,
|
xpubs: Vec<String>,
|
||||||
next_account: ChildNumber,
|
next_account: ChildNumber,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SignerXpubs {
|
impl SignerXpubs {
|
||||||
fn new(signer: Arc<Signer>) -> Self {
|
fn new(signer: Arc<Mutex<Signer>>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
signer,
|
signer,
|
||||||
xpubs: Vec::new(),
|
xpubs: Vec::new(),
|
||||||
@ -1095,11 +1083,12 @@ impl SignerXpubs {
|
|||||||
fn select(&mut self, network: Network) {
|
fn select(&mut self, network: Network) {
|
||||||
let derivation_path = generate_derivation_path(network, self.next_account);
|
let derivation_path = generate_derivation_path(network, self.next_account);
|
||||||
self.next_account = self.next_account.increment().unwrap();
|
self.next_account = self.next_account.increment().unwrap();
|
||||||
|
let signer = self.signer.lock().unwrap();
|
||||||
self.xpubs.push(format!(
|
self.xpubs.push(format!(
|
||||||
"[{}{}]{}",
|
"[{}{}]{}",
|
||||||
self.signer.fingerprint(),
|
signer.fingerprint(),
|
||||||
derivation_path.to_string().trim_start_matches('m'),
|
derivation_path.to_string().trim_start_matches('m'),
|
||||||
self.signer.get_extended_pubkey(&derivation_path)
|
signer.get_extended_pubkey(&derivation_path)
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1120,14 +1109,14 @@ pub struct ParticipateXpub {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ParticipateXpub {
|
impl ParticipateXpub {
|
||||||
pub fn new() -> Self {
|
pub fn new(signer: Arc<Mutex<Signer>>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
network: Network::Bitcoin,
|
network: Network::Bitcoin,
|
||||||
network_valid: true,
|
network_valid: true,
|
||||||
data_dir: None,
|
data_dir: None,
|
||||||
xpubs_hw: Vec::new(),
|
xpubs_hw: Vec::new(),
|
||||||
shared: false,
|
shared: false,
|
||||||
xpubs_signer: SignerXpubs::new(Arc::new(Signer::generate(Network::Bitcoin).unwrap())),
|
xpubs_signer: SignerXpubs::new(signer),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1137,9 +1126,11 @@ impl ParticipateXpub {
|
|||||||
self.xpubs_signer.reset();
|
self.xpubs_signer.reset();
|
||||||
}
|
}
|
||||||
self.network = network;
|
self.network = network;
|
||||||
if let Some(signer) = Arc::get_mut(&mut self.xpubs_signer.signer) {
|
self.xpubs_signer
|
||||||
signer.set_network(network);
|
.signer
|
||||||
}
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.set_network(network);
|
||||||
if let Some(mut network_datadir) = self.data_dir.clone() {
|
if let Some(mut network_datadir) = self.data_dir.clone() {
|
||||||
network_datadir.push(self.network.to_string());
|
network_datadir.push(self.network.to_string());
|
||||||
self.network_valid = !network_datadir.exists();
|
self.network_valid = !network_datadir.exists();
|
||||||
@ -1147,12 +1138,6 @@ impl ParticipateXpub {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for ParticipateXpub {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::new()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Step for ParticipateXpub {
|
impl Step for ParticipateXpub {
|
||||||
// form value is set as valid each time it is edited.
|
// form value is set as valid each time it is edited.
|
||||||
// Verification of the values is happening when the user click on Next button.
|
// Verification of the values is happening when the user click on Next button.
|
||||||
@ -1211,11 +1196,6 @@ impl Step for ParticipateXpub {
|
|||||||
ctx.bitcoin_config.network = self.network;
|
ctx.bitcoin_config.network = self.network;
|
||||||
// Drop connections to hardware wallets.
|
// Drop connections to hardware wallets.
|
||||||
self.xpubs_hw = Vec::new();
|
self.xpubs_hw = Vec::new();
|
||||||
if !self.xpubs_signer.xpubs.is_empty() {
|
|
||||||
ctx.signer = Some(self.xpubs_signer.signer.clone());
|
|
||||||
} else {
|
|
||||||
ctx.signer = None;
|
|
||||||
}
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1515,7 +1495,9 @@ mod tests {
|
|||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_define_descriptor_use_hotkey() {
|
async fn test_define_descriptor_use_hotkey() {
|
||||||
let mut ctx = Context::new(Network::Signet, PathBuf::from_str("/").unwrap());
|
let mut ctx = Context::new(Network::Signet, PathBuf::from_str("/").unwrap());
|
||||||
let sandbox: Sandbox<DefineDescriptor> = Sandbox::new(DefineDescriptor::new());
|
let sandbox: Sandbox<DefineDescriptor> = Sandbox::new(DefineDescriptor::new(Arc::new(
|
||||||
|
Mutex::new(Signer::generate(Network::Bitcoin).unwrap()),
|
||||||
|
)));
|
||||||
|
|
||||||
// Edit primary key
|
// Edit primary key
|
||||||
sandbox
|
sandbox
|
||||||
@ -1582,14 +1564,21 @@ mod tests {
|
|||||||
sandbox.check(|step| {
|
sandbox.check(|step| {
|
||||||
assert!(step.modal.is_none());
|
assert!(step.modal.is_none());
|
||||||
assert!((step).apply(&mut ctx));
|
assert!((step).apply(&mut ctx));
|
||||||
assert!(ctx.signer.is_some());
|
assert!(ctx
|
||||||
|
.descriptor
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.to_string()
|
||||||
|
.contains(&step.signer.lock().unwrap().fingerprint().to_string()));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_define_descriptor_stores_if_hw_is_used() {
|
async fn test_define_descriptor_stores_if_hw_is_used() {
|
||||||
let mut ctx = Context::new(Network::Signet, PathBuf::from_str("/").unwrap());
|
let mut ctx = Context::new(Network::Signet, PathBuf::from_str("/").unwrap());
|
||||||
let sandbox: Sandbox<DefineDescriptor> = Sandbox::new(DefineDescriptor::new());
|
let sandbox: Sandbox<DefineDescriptor> = Sandbox::new(DefineDescriptor::new(Arc::new(
|
||||||
|
Mutex::new(Signer::generate(Network::Bitcoin).unwrap()),
|
||||||
|
)));
|
||||||
|
|
||||||
let specter_key = message::DefinePath::Key(
|
let specter_key = message::DefinePath::Key(
|
||||||
0,
|
0,
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::sync::Arc;
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
use iced::Command;
|
use iced::Command;
|
||||||
use liana::{bip39, signer::HotSigner};
|
use liana::{bip39, signer::HotSigner};
|
||||||
@ -11,10 +11,21 @@ use crate::{
|
|||||||
signer::Signer,
|
signer::Signer,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct BackupMnemonic {
|
pub struct BackupMnemonic {
|
||||||
words: [&'static str; 12],
|
words: [&'static str; 12],
|
||||||
done: bool,
|
done: bool,
|
||||||
|
signer: Arc<Mutex<Signer>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BackupMnemonic {
|
||||||
|
pub fn new(signer: Arc<Mutex<Signer>>) -> Self {
|
||||||
|
let words = signer.lock().unwrap().mnemonic();
|
||||||
|
Self {
|
||||||
|
done: false,
|
||||||
|
words,
|
||||||
|
signer,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<BackupMnemonic> for Box<dyn Step> {
|
impl From<BackupMnemonic> for Box<dyn Step> {
|
||||||
@ -24,11 +35,6 @@ impl From<BackupMnemonic> for Box<dyn Step> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Step for BackupMnemonic {
|
impl Step for BackupMnemonic {
|
||||||
fn load_context(&mut self, ctx: &Context) {
|
|
||||||
if let Some(signer) = &ctx.signer {
|
|
||||||
self.words = signer.mnemonic();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn update(&mut self, message: Message) -> Command<Message> {
|
fn update(&mut self, message: Message) -> Command<Message> {
|
||||||
if let Message::UserActionDone(done) = message {
|
if let Message::UserActionDone(done) = message {
|
||||||
self.done = done;
|
self.done = done;
|
||||||
@ -36,7 +42,13 @@ impl Step for BackupMnemonic {
|
|||||||
Command::none()
|
Command::none()
|
||||||
}
|
}
|
||||||
fn skip(&self, ctx: &Context) -> bool {
|
fn skip(&self, ctx: &Context) -> bool {
|
||||||
ctx.signer.is_none()
|
if let Some(descriptor) = &ctx.descriptor {
|
||||||
|
!descriptor
|
||||||
|
.to_string()
|
||||||
|
.contains(&self.signer.lock().unwrap().fingerprint().to_string())
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
fn view(&self, progress: (usize, usize)) -> Element<Message> {
|
fn view(&self, progress: (usize, usize)) -> Element<Message> {
|
||||||
view::backup_mnemonic(progress, &self.words, self.done)
|
view::backup_mnemonic(progress, &self.words, self.done)
|
||||||
@ -109,7 +121,6 @@ impl Step for RecoverMnemonic {
|
|||||||
if self.skip {
|
if self.skip {
|
||||||
// If the user click previous, we dont want the skip to be set to true.
|
// If the user click previous, we dont want the skip to be set to true.
|
||||||
self.skip = false;
|
self.skip = false;
|
||||||
ctx.signer = None;
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -148,8 +159,7 @@ impl Step for RecoverMnemonic {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.signer = Some(Arc::new(signer));
|
ctx.recovered_signer = Some(Arc::new(signer));
|
||||||
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
fn view(&self, progress: (usize, usize)) -> Element<Message> {
|
fn view(&self, progress: (usize, usize)) -> Element<Message> {
|
||||||
|
|||||||
@ -11,7 +11,10 @@ use std::path::PathBuf;
|
|||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use iced::Command;
|
use iced::Command;
|
||||||
use liana::{config::BitcoindConfig, miniscript::bitcoin};
|
use liana::{
|
||||||
|
config::BitcoindConfig,
|
||||||
|
miniscript::bitcoin::{util::bip32::Fingerprint, Network},
|
||||||
|
};
|
||||||
|
|
||||||
use jsonrpc::{client::Client, simple_http::SimpleHttpTransport};
|
use jsonrpc::{client::Client, simple_http::SimpleHttpTransport};
|
||||||
|
|
||||||
@ -61,7 +64,7 @@ pub struct DefineBitcoind {
|
|||||||
is_running: Option<Result<(), Error>>,
|
is_running: Option<Result<(), Error>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn bitcoind_default_cookie_path(network: &bitcoin::Network) -> Option<String> {
|
fn bitcoind_default_cookie_path(network: &Network) -> Option<String> {
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
let configs_dir = dirs::home_dir();
|
let configs_dir = dirs::home_dir();
|
||||||
|
|
||||||
@ -76,16 +79,16 @@ fn bitcoind_default_cookie_path(network: &bitcoin::Network) -> Option<String> {
|
|||||||
path.push("Bitcoin");
|
path.push("Bitcoin");
|
||||||
|
|
||||||
match network {
|
match network {
|
||||||
bitcoin::Network::Bitcoin => {
|
Network::Bitcoin => {
|
||||||
path.push(".cookie");
|
path.push(".cookie");
|
||||||
}
|
}
|
||||||
bitcoin::Network::Testnet => {
|
Network::Testnet => {
|
||||||
path.push("testnet3/.cookie");
|
path.push("testnet3/.cookie");
|
||||||
}
|
}
|
||||||
bitcoin::Network::Regtest => {
|
Network::Regtest => {
|
||||||
path.push("regtest/.cookie");
|
path.push("regtest/.cookie");
|
||||||
}
|
}
|
||||||
bitcoin::Network::Signet => {
|
Network::Signet => {
|
||||||
path.push("signet/.cookie");
|
path.push("signet/.cookie");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -95,12 +98,12 @@ fn bitcoind_default_cookie_path(network: &bitcoin::Network) -> Option<String> {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
fn bitcoind_default_address(network: &bitcoin::Network) -> String {
|
fn bitcoind_default_address(network: &Network) -> String {
|
||||||
match network {
|
match network {
|
||||||
bitcoin::Network::Bitcoin => "127.0.0.1:8332".to_string(),
|
Network::Bitcoin => "127.0.0.1:8332".to_string(),
|
||||||
bitcoin::Network::Testnet => "127.0.0.1:18332".to_string(),
|
Network::Testnet => "127.0.0.1:18332".to_string(),
|
||||||
bitcoin::Network::Regtest => "127.0.0.1:18443".to_string(),
|
Network::Regtest => "127.0.0.1:18443".to_string(),
|
||||||
bitcoin::Network::Signet => "127.0.0.1:38332".to_string(),
|
Network::Signet => "127.0.0.1:38332".to_string(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -227,15 +230,19 @@ pub struct Final {
|
|||||||
context: Option<Context>,
|
context: Option<Context>,
|
||||||
warning: Option<String>,
|
warning: Option<String>,
|
||||||
config_path: Option<PathBuf>,
|
config_path: Option<PathBuf>,
|
||||||
|
hot_signer_fingerprint: Fingerprint,
|
||||||
|
hot_signer_is_not_used: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Final {
|
impl Final {
|
||||||
pub fn new() -> Self {
|
pub fn new(hot_signer_fingerprint: Fingerprint) -> Self {
|
||||||
Self {
|
Self {
|
||||||
context: None,
|
context: None,
|
||||||
generating: false,
|
generating: false,
|
||||||
warning: None,
|
warning: None,
|
||||||
config_path: None,
|
config_path: None,
|
||||||
|
hot_signer_fingerprint,
|
||||||
|
hot_signer_is_not_used: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -243,6 +250,20 @@ impl Final {
|
|||||||
impl Step for Final {
|
impl Step for Final {
|
||||||
fn load_context(&mut self, ctx: &Context) {
|
fn load_context(&mut self, ctx: &Context) {
|
||||||
self.context = Some(ctx.clone());
|
self.context = Some(ctx.clone());
|
||||||
|
if let Some(signer) = &ctx.recovered_signer {
|
||||||
|
self.hot_signer_fingerprint = signer.fingerprint();
|
||||||
|
self.hot_signer_is_not_used = false;
|
||||||
|
} else if ctx
|
||||||
|
.descriptor
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.to_string()
|
||||||
|
.contains(&self.hot_signer_fingerprint.to_string())
|
||||||
|
{
|
||||||
|
self.hot_signer_is_not_used = false;
|
||||||
|
} else {
|
||||||
|
self.hot_signer_is_not_used = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
fn update(&mut self, message: Message) -> Command<Message> {
|
fn update(&mut self, message: Message) -> Command<Message> {
|
||||||
match message {
|
match message {
|
||||||
@ -276,16 +297,15 @@ impl Step for Final {
|
|||||||
self.generating,
|
self.generating,
|
||||||
self.config_path.as_ref(),
|
self.config_path.as_ref(),
|
||||||
self.warning.as_ref(),
|
self.warning.as_ref(),
|
||||||
|
if self.hot_signer_is_not_used {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(self.hot_signer_fingerprint)
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Final {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::new()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Final> for Box<dyn Step> {
|
impl From<Final> for Box<dyn Step> {
|
||||||
fn from(s: Final) -> Box<dyn Step> {
|
fn from(s: Final) -> Box<dyn Step> {
|
||||||
Box::new(s)
|
Box::new(s)
|
||||||
|
|||||||
@ -25,7 +25,6 @@ use crate::{
|
|||||||
message::{self, Message},
|
message::{self, Message},
|
||||||
prompt, Error,
|
prompt, Error,
|
||||||
},
|
},
|
||||||
signer::Signer,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
@ -888,6 +887,7 @@ pub fn install<'a>(
|
|||||||
generating: bool,
|
generating: bool,
|
||||||
config_path: Option<&std::path::PathBuf>,
|
config_path: Option<&std::path::PathBuf>,
|
||||||
warning: Option<&'a String>,
|
warning: Option<&'a String>,
|
||||||
|
signer: Option<Fingerprint>,
|
||||||
) -> Element<'a, Message> {
|
) -> Element<'a, Message> {
|
||||||
layout(
|
layout(
|
||||||
progress,
|
progress,
|
||||||
@ -911,7 +911,7 @@ pub fn install<'a>(
|
|||||||
)
|
)
|
||||||
.width(Length::Fill),
|
.width(Length::Fill),
|
||||||
)
|
)
|
||||||
.push_maybe(if context.hws.is_empty() && context.signer.is_none() {
|
.push_maybe(if context.hws.is_empty() && signer.is_none() {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
Some(
|
Some(
|
||||||
@ -950,21 +950,17 @@ pub fn install<'a>(
|
|||||||
},
|
},
|
||||||
))
|
))
|
||||||
})
|
})
|
||||||
.push_maybe(context.signer.as_ref().map(|signer| {
|
.push_maybe(signer.as_ref().map(|fingerprint| {
|
||||||
Row::new()
|
Row::new()
|
||||||
.spacing(5)
|
.spacing(5)
|
||||||
.push_maybe(context.keys.iter().find_map(|k| {
|
.push_maybe(context.keys.iter().find_map(|k| {
|
||||||
if k.master_fingerprint == signer.fingerprint()
|
if k.master_fingerprint == *fingerprint {
|
||||||
{
|
|
||||||
Some(text(k.name.clone()).small().bold())
|
Some(text(k.name.clone()).small().bold())
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
.push(
|
.push(text(format!("#{}", fingerprint)).small())
|
||||||
text(format!("#{}", signer.fingerprint()))
|
|
||||||
.small(),
|
|
||||||
)
|
|
||||||
.push(text("This computer").small())
|
.push(text("This computer").small())
|
||||||
})),
|
})),
|
||||||
)
|
)
|
||||||
@ -1245,7 +1241,7 @@ pub fn edit_key_modal<'a>(
|
|||||||
error: Option<&Error>,
|
error: Option<&Error>,
|
||||||
processing: bool,
|
processing: bool,
|
||||||
chosen_signer: Option<Fingerprint>,
|
chosen_signer: Option<Fingerprint>,
|
||||||
signer: &Signer,
|
hot_signer_fingerprint: &Fingerprint,
|
||||||
signer_alias: Option<&'a String>,
|
signer_alias: Option<&'a String>,
|
||||||
form_xpub: &form::Value<String>,
|
form_xpub: &form::Value<String>,
|
||||||
form_name: &'a form::Value<String>,
|
form_name: &'a form::Value<String>,
|
||||||
@ -1288,10 +1284,10 @@ pub fn edit_key_modal<'a>(
|
|||||||
},
|
},
|
||||||
))
|
))
|
||||||
.push(
|
.push(
|
||||||
Button::new(if Some(signer.fingerprint) == chosen_signer {
|
Button::new(if Some(*hot_signer_fingerprint) == chosen_signer {
|
||||||
hw::selected_hot_signer(signer.fingerprint, signer_alias)
|
hw::selected_hot_signer(hot_signer_fingerprint, signer_alias)
|
||||||
} else {
|
} else {
|
||||||
hw::unselected_hot_signer(signer.fingerprint, signer_alias)
|
hw::unselected_hot_signer(hot_signer_fingerprint, signer_alias)
|
||||||
})
|
})
|
||||||
.width(Length::Fill)
|
.width(Length::Fill)
|
||||||
.on_press(Message::UseHotSigner)
|
.on_press(Message::UseHotSigner)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user