Merge #1399: Template multisig security
36af35fae3f21995fcbe0c4c1a5f685a2318d0a0 refac: remove select button at the choose template step (edouardparis) 2eddbbd7000ccb8e392a3da4fd854651bf604a80 change wording in inheritance template (edouardparis) 789c2aebfb3731b43b9d1750d5586db1456c14fc Add new template: multisig security wallet (edouardparis) Pull request description:   ACKs for top commit: edouardparis: Self-ACK 36af35fae3f21995fcbe0c4c1a5f685a2318d0a0 Tree-SHA512: 4e6617adb2eb7f0932aca8260f6fad120dcdb2aafc2c0880036bddc31874b1ba4280dbb27eb495d601a10948fb3aaf40ca4c5fd8922fc8660fc269a4c80a5cff
This commit is contained in:
commit
2d5991cb0c
@ -50,6 +50,7 @@ pub enum DescriptorTemplate {
|
||||
#[default]
|
||||
SimpleInheritance,
|
||||
Custom,
|
||||
MultisigSecurity,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
|
||||
@ -113,6 +113,8 @@ pub enum InternalBitcoindMsg {
|
||||
pub enum DefineDescriptor {
|
||||
ChangeTemplate(context::DescriptorTemplate),
|
||||
ImportDescriptor(String),
|
||||
KeysEdited(Vec<(usize, usize)>, Key),
|
||||
KeysEdit(Vec<(usize, usize)>),
|
||||
Path(usize, DefinePath),
|
||||
AddRecoveryPath,
|
||||
KeyModal(ImportKeyModal),
|
||||
@ -136,7 +138,6 @@ pub enum DefineKey {
|
||||
Delete,
|
||||
Edit,
|
||||
Clipboard(String),
|
||||
Edited(Key),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
||||
@ -74,8 +74,7 @@ pub fn check_key_network(key: &DescriptorPublicKey, network: Network) -> bool {
|
||||
|
||||
pub struct EditXpubModal {
|
||||
device_must_support_tapminiscript: bool,
|
||||
path_index: usize,
|
||||
key_index: usize,
|
||||
keys_coordinate: Vec<(usize, usize)>,
|
||||
network: Network,
|
||||
error: Option<Error>,
|
||||
processing: bool,
|
||||
@ -99,8 +98,7 @@ impl EditXpubModal {
|
||||
device_must_support_tapminiscript: bool,
|
||||
other_path_keys: HashSet<Fingerprint>,
|
||||
key: Option<Key>,
|
||||
path_index: usize,
|
||||
key_index: usize,
|
||||
keys_coordinate: Vec<(usize, usize)>,
|
||||
network: Network,
|
||||
hot_signer: Arc<Mutex<Signer>>,
|
||||
hot_signer_fingerprint: Fingerprint,
|
||||
@ -128,8 +126,7 @@ impl EditXpubModal {
|
||||
},
|
||||
manually_imported_xpub,
|
||||
keys,
|
||||
path_index,
|
||||
key_index,
|
||||
keys_coordinate,
|
||||
processing: false,
|
||||
error: None,
|
||||
network,
|
||||
@ -304,22 +301,15 @@ impl super::DescriptorEditModal for EditXpubModal {
|
||||
}
|
||||
message::ImportKeyModal::ConfirmXpub => {
|
||||
if let Some(mut key) = self.chosen_signer.clone() {
|
||||
let key_index = self.key_index;
|
||||
key.name.clone_from(&self.form_name.value);
|
||||
if self.other_path_keys.contains(&key.fingerprint) {
|
||||
self.duplicate_master_fg = true;
|
||||
} else {
|
||||
let path_index = self.path_index;
|
||||
let coordinate = self.keys_coordinate.clone();
|
||||
return Command::perform(
|
||||
async move { (path_index, key_index, key) },
|
||||
move |(path_index, key_index, key)| {
|
||||
message::DefineDescriptor::Path(
|
||||
path_index,
|
||||
message::DefinePath::Key(
|
||||
key_index,
|
||||
message::DefineKey::Edited(key),
|
||||
),
|
||||
)
|
||||
async move { (coordinate, key) },
|
||||
move |(coordinate, key)| {
|
||||
message::DefineDescriptor::KeysEdited(coordinate, key)
|
||||
},
|
||||
)
|
||||
.map(Message::DefineDescriptor);
|
||||
@ -346,11 +336,7 @@ impl super::DescriptorEditModal for EditXpubModal {
|
||||
fn view<'a>(&'a self, hws: &'a HardwareWallets) -> Element<'a, Message> {
|
||||
let chosen_signer = self.chosen_signer.as_ref().map(|s| s.fingerprint);
|
||||
view::editor::edit_key_modal(
|
||||
if self.path_index > 0 {
|
||||
"Set your key"
|
||||
} else {
|
||||
"Set your primary key"
|
||||
},
|
||||
"Set your key",
|
||||
self.network,
|
||||
hws.list
|
||||
.iter()
|
||||
|
||||
@ -74,6 +74,23 @@ impl Path {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_n_keys(mut self, n: usize) -> Self {
|
||||
self.keys = Vec::new();
|
||||
for _i in 0..n {
|
||||
self.keys.push(None);
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_threshold(mut self, t: usize) -> Self {
|
||||
self.threshold = if t > self.keys.len() {
|
||||
self.keys.len()
|
||||
} else {
|
||||
t
|
||||
};
|
||||
self
|
||||
}
|
||||
|
||||
fn valid(&self) -> bool {
|
||||
!self.keys.is_empty() && !self.keys.iter().any(|k| k.is_none()) && !self.duplicate_sequence
|
||||
}
|
||||
@ -164,6 +181,12 @@ impl DefineDescriptor {
|
||||
DescriptorTemplate::SimpleInheritance => {
|
||||
self.paths = vec![Path::new_primary_path(), Path::new_recovery_path()];
|
||||
}
|
||||
DescriptorTemplate::MultisigSecurity => {
|
||||
self.paths = vec![
|
||||
Path::new_primary_path().with_n_keys(2).with_threshold(2),
|
||||
Path::new_recovery_path().with_n_keys(3).with_threshold(2),
|
||||
];
|
||||
}
|
||||
DescriptorTemplate::Custom => {
|
||||
self.paths = vec![Path::new_primary_path(), Path::new_recovery_path()];
|
||||
}
|
||||
@ -195,6 +218,46 @@ impl Step for DefineDescriptor {
|
||||
Message::DefineDescriptor(message::DefineDescriptor::AddRecoveryPath) => {
|
||||
self.paths.push(Path::new_recovery_path());
|
||||
}
|
||||
Message::DefineDescriptor(message::DefineDescriptor::KeysEdited(coordinate, key)) => {
|
||||
hws.set_alias(key.fingerprint, key.name.clone());
|
||||
for (i, j) in coordinate {
|
||||
self.paths[i].keys[j] = Some(key.fingerprint);
|
||||
}
|
||||
self.keys.insert(key.fingerprint, key);
|
||||
self.modal = None;
|
||||
self.check_setup();
|
||||
}
|
||||
Message::DefineDescriptor(message::DefineDescriptor::KeysEdit(coordinate)) => {
|
||||
let use_taproot = self.use_taproot;
|
||||
let mut set = HashSet::<Fingerprint>::new();
|
||||
let key = coordinate
|
||||
.first()
|
||||
.and_then(|(i, j)| self.paths[*i].keys[*j])
|
||||
.and_then(|f| self.keys.get(&f))
|
||||
.cloned();
|
||||
for (i, j) in &coordinate {
|
||||
set.extend(self.paths[*i].keys.iter().filter_map(|key| {
|
||||
if key.is_some() && key != &self.paths[*i].keys[*j] {
|
||||
*key
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}));
|
||||
}
|
||||
let modal = EditXpubModal::new(
|
||||
use_taproot,
|
||||
set,
|
||||
key,
|
||||
coordinate,
|
||||
self.network,
|
||||
self.signer.clone(),
|
||||
self.signer_fingerprint,
|
||||
self.keys.values().cloned().collect(),
|
||||
);
|
||||
let cmd = modal.load();
|
||||
self.modal = Some(Box::new(modal));
|
||||
return cmd;
|
||||
}
|
||||
Message::DefineDescriptor(message::DefineDescriptor::Path(i, msg)) => match msg {
|
||||
message::DefinePath::SequenceEdited(seq) => {
|
||||
self.modal = None;
|
||||
@ -233,13 +296,7 @@ impl Step for DefineDescriptor {
|
||||
message::DefineKey::Clipboard(key) => {
|
||||
return Command::perform(async move { key }, Message::Clibpboard);
|
||||
}
|
||||
message::DefineKey::Edited(key) => {
|
||||
hws.set_alias(key.fingerprint, key.name.clone());
|
||||
self.paths[i].keys[j] = Some(key.fingerprint);
|
||||
self.keys.insert(key.fingerprint, key);
|
||||
self.modal = None;
|
||||
self.check_setup();
|
||||
}
|
||||
|
||||
message::DefineKey::Edit => {
|
||||
let use_taproot = self.use_taproot;
|
||||
let path = &self.paths[i];
|
||||
@ -253,8 +310,7 @@ impl Step for DefineDescriptor {
|
||||
}
|
||||
})),
|
||||
path.keys[j].and_then(|f| self.keys.get(&f)).cloned(),
|
||||
i,
|
||||
j,
|
||||
vec![(i, j)],
|
||||
self.network,
|
||||
self.signer.clone(),
|
||||
self.signer_fingerprint,
|
||||
@ -424,6 +480,17 @@ impl Step for DefineDescriptor {
|
||||
self.valid(),
|
||||
)
|
||||
}
|
||||
DescriptorTemplate::MultisigSecurity => {
|
||||
view::editor::template::multisig_security_wallet::multisig_security_template(
|
||||
progress,
|
||||
self.use_taproot,
|
||||
self.path_keys(&self.paths[0]),
|
||||
self.path_keys(&self.paths[1]),
|
||||
self.paths[1].sequence,
|
||||
self.paths[1].threshold,
|
||||
self.valid(),
|
||||
)
|
||||
}
|
||||
DescriptorTemplate::Custom => view::editor::template::custom::custom_template(
|
||||
progress,
|
||||
self.use_taproot,
|
||||
@ -714,25 +781,21 @@ mod tests {
|
||||
sandbox.load(&ctx).await;
|
||||
|
||||
let key = DescriptorPublicKey::from_str("[4df3f0e3/84'/0'/0']tpubDDRs9DnRUiJc4hq92PSJKhfzQBgHJUrDo7T2i48smsDfLsQcm3Vh7JhuGqJv8zozVkNFin8YPgpmn2NWNmpRaE3GW2pSxbmAzYf2juy7LeW").unwrap();
|
||||
let specter_key = message::DefinePath::Key(
|
||||
0,
|
||||
message::DefineKey::Edited(Key {
|
||||
name: "My Specter key".to_string(),
|
||||
fingerprint: key.master_fingerprint(),
|
||||
key,
|
||||
device_kind: Some(async_hwi::DeviceKind::Specter),
|
||||
device_version: None,
|
||||
is_compatible_taproot: false,
|
||||
is_hot_signer: false,
|
||||
}),
|
||||
);
|
||||
let specter_key = Key {
|
||||
name: "My Specter key".to_string(),
|
||||
fingerprint: key.master_fingerprint(),
|
||||
key,
|
||||
device_kind: Some(async_hwi::DeviceKind::Specter),
|
||||
device_version: None,
|
||||
is_compatible_taproot: false,
|
||||
is_hot_signer: false,
|
||||
};
|
||||
|
||||
// Use Specter device for primary key
|
||||
sandbox
|
||||
.update(Message::DefineDescriptor(message::DefineDescriptor::Path(
|
||||
0,
|
||||
specter_key.clone(),
|
||||
)))
|
||||
.update(Message::DefineDescriptor(
|
||||
message::DefineDescriptor::KeysEdited(vec![(0, 0)], specter_key.clone()),
|
||||
))
|
||||
.await;
|
||||
|
||||
// Edit recovery key
|
||||
@ -795,10 +858,9 @@ mod tests {
|
||||
|
||||
// Now edit the recovery key to use Specter device
|
||||
sandbox
|
||||
.update(Message::DefineDescriptor(message::DefineDescriptor::Path(
|
||||
1,
|
||||
specter_key.clone(),
|
||||
)))
|
||||
.update(Message::DefineDescriptor(
|
||||
message::DefineDescriptor::KeysEdited(vec![(1, 0)], specter_key.clone()),
|
||||
))
|
||||
.await;
|
||||
sandbox.check(|step| {
|
||||
assert!((step).apply(&mut ctx));
|
||||
|
||||
@ -73,7 +73,10 @@ impl Step for DescriptorTemplateDescription {
|
||||
DescriptorTemplate::SimpleInheritance => {
|
||||
view::editor::template::inheritance::inheritance_template_description(progress)
|
||||
}
|
||||
DescriptorTemplate::Custom { .. } => {
|
||||
DescriptorTemplate::MultisigSecurity => {
|
||||
view::editor::template::multisig_security_wallet::multisig_security_template_description(progress)
|
||||
}
|
||||
DescriptorTemplate::Custom => {
|
||||
view::editor::template::custom::custom_template_description(progress)
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,6 +6,7 @@ use iced::{Alignment, Length};
|
||||
use liana::miniscript::bitcoin::Network;
|
||||
use liana_ui::component::text::{self, h3, p1_bold, p2_regular, H3_SIZE};
|
||||
use liana_ui::image;
|
||||
use std::borrow::Cow;
|
||||
use std::str::FromStr;
|
||||
|
||||
use liana::miniscript::bitcoin::{self, bip32::Fingerprint};
|
||||
@ -130,7 +131,7 @@ pub fn path(
|
||||
pub fn defined_key<'a>(
|
||||
alias: &'a str,
|
||||
color: iced::Color,
|
||||
title: &'static str,
|
||||
title: impl Into<Cow<'a, str>>,
|
||||
warning: Option<&'static str>,
|
||||
fixed: bool,
|
||||
) -> Element<'a, message::DefineKey> {
|
||||
@ -177,7 +178,7 @@ pub fn defined_key<'a>(
|
||||
|
||||
pub fn undefined_key<'a>(
|
||||
color: iced::Color,
|
||||
title: &'static str,
|
||||
title: impl Into<Cow<'a, str>>,
|
||||
active: bool,
|
||||
fixed: bool,
|
||||
) -> Element<'a, message::DefineKey> {
|
||||
|
||||
@ -4,7 +4,7 @@ use liana_ui::{
|
||||
color,
|
||||
component::{
|
||||
button, collapse,
|
||||
text::{h3, p1_regular, text, Text},
|
||||
text::{h3, p1_regular, text, Text, H3_SIZE},
|
||||
},
|
||||
icon, image, theme,
|
||||
widget::*,
|
||||
@ -30,16 +30,32 @@ pub fn inheritance_template_description(progress: (usize, usize)) -> Element<'st
|
||||
.push(h3("Inheritance wallet"))
|
||||
.max_width(800.0)
|
||||
.push(Container::new(
|
||||
p1_regular("For this Inheritance wallet you will need 2 Keys: Your Primary Key and an Inheritance Key to be given to a chosen heir. For security reasons, we suggest you use 2 Hardware Wallets to store them.")
|
||||
p1_regular("For this Inheritance wallet you will need 2 Keys: Your Primary Key (for yourself) and an Inheritance Key (for your heir). For security reasons, we suggest you use 2 Hardware Wallets to store them.")
|
||||
.style(color::GREY_2)
|
||||
.horizontal_alignment(alignment::Horizontal::Left)
|
||||
).align_x(alignment::Horizontal::Left).width(Length::Fill))
|
||||
.push(Row::new()
|
||||
.spacing(30)
|
||||
.push(
|
||||
Row::new()
|
||||
.align_items(Alignment::Center)
|
||||
.spacing(10)
|
||||
.push(icon::round_key_icon().size(H3_SIZE).style(color::GREEN))
|
||||
.push(p1_regular("Primary key").bold())
|
||||
).push(
|
||||
Row::new()
|
||||
.align_items(Alignment::Center)
|
||||
.spacing(10)
|
||||
.push(icon::round_key_icon().size(H3_SIZE).style(color::WHITE))
|
||||
.push(p1_regular("Inheritance key").bold())
|
||||
))
|
||||
.push(Container::new(
|
||||
p1_regular("You will always be able to spend using your Primary Key.
|
||||
After a period of inactivity (but not before that) your Inheritance Key will become able to recover your funds. Give it to your heir(s) in order to be able to collect their inheritance.")
|
||||
.style(color::GREY_2)
|
||||
.horizontal_alignment(alignment::Horizontal::Left)
|
||||
).align_x(alignment::Horizontal::Left).width(Length::Fill))
|
||||
.push(image::inheritance_template_description().width(Length::Fill))
|
||||
.push(Container::new(
|
||||
p1_regular("Your relative’s Inheritance Key will become active only if you don’t move the coins in your wallet for the defined period of time, enabling him/her to recover your funds while not being able to access them before that.")
|
||||
.style(color::GREY_2)
|
||||
.horizontal_alignment(alignment::Horizontal::Left)
|
||||
).align_x(alignment::Horizontal::Left).width(Length::Fill))
|
||||
.push(Row::new().push(Space::with_width(Length::Fill)).push(button::primary(None, "Select").width(Length::Fixed(200.0)).on_press(Message::Next)))
|
||||
.spacing(20),
|
||||
true,
|
||||
|
||||
@ -1,14 +1,13 @@
|
||||
pub mod custom;
|
||||
pub mod inheritance;
|
||||
pub mod multisig_security_wallet;
|
||||
|
||||
use iced::{alignment, Alignment, Length};
|
||||
|
||||
use liana_ui::{
|
||||
color,
|
||||
component::{
|
||||
button, card,
|
||||
text::{h3, p1_regular, p2_regular},
|
||||
},
|
||||
component::text::{h3, p1_regular, p2_regular},
|
||||
theme,
|
||||
widget::*,
|
||||
};
|
||||
|
||||
@ -25,45 +24,55 @@ pub fn choose_descriptor_template(progress: (usize, usize)) -> Element<'static,
|
||||
.align_items(Alignment::Start)
|
||||
.push(Container::new(
|
||||
p1_regular("What do you want your wallet for? This depends on the amount of funds you have, the more funds, the higher the security should be. Not sure about the wallet type? We can help you.")
|
||||
.style(color::GREY_2)
|
||||
.horizontal_alignment(alignment::Horizontal::Left)
|
||||
.style(color::GREY_2)
|
||||
.horizontal_alignment(alignment::Horizontal::Left)
|
||||
).align_x(alignment::Horizontal::Left).width(Length::Fill))
|
||||
.push(
|
||||
card::simple(
|
||||
Row::new()
|
||||
.align_items(Alignment::Center)
|
||||
.push(
|
||||
Column::new()
|
||||
.align_items(Alignment::Start)
|
||||
.push(h3("Simple inheritance"))
|
||||
.push(p2_regular("Two keys required, one for yourself to spend and another for your heir.").style(color::GREY_2))
|
||||
.width(Length::Fill)
|
||||
)
|
||||
.push(button::secondary(None, "Select").on_press(
|
||||
Message::SelectDescriptorTemplate(
|
||||
context::DescriptorTemplate::SimpleInheritance,
|
||||
),
|
||||
)),
|
||||
Button::new(
|
||||
Column::new()
|
||||
.align_items(Alignment::Start)
|
||||
.push(h3("Simple inheritance"))
|
||||
.push(p2_regular("Two keys required, one for yourself to spend and another for your heir.").style(color::GREY_2))
|
||||
.width(Length::Fill)
|
||||
)
|
||||
.padding(15)
|
||||
.on_press(
|
||||
Message::SelectDescriptorTemplate(
|
||||
context::DescriptorTemplate::SimpleInheritance,
|
||||
)
|
||||
).style(theme::Button::Secondary)
|
||||
.width(Length::Fill),
|
||||
)
|
||||
.push(
|
||||
card::simple(
|
||||
Row::new()
|
||||
.align_items(Alignment::Center)
|
||||
.push(
|
||||
Column::new()
|
||||
.align_items(Alignment::Start)
|
||||
.push(h3("Custom (choose your own)"))
|
||||
.push(p2_regular("Create a custom setup that fits all your needs").style(color::GREY_2))
|
||||
.width(Length::Fill)
|
||||
)
|
||||
.push(button::secondary(None, "Select").on_press(
|
||||
Message::SelectDescriptorTemplate(
|
||||
context::DescriptorTemplate::Custom ,
|
||||
),
|
||||
)),
|
||||
Button::new(
|
||||
Column::new()
|
||||
.align_items(Alignment::Start)
|
||||
.push(h3("Multisig security wallet"))
|
||||
.push(p2_regular("A secure scheme requiring stricter multiparty signature and recovery.").style(color::GREY_2))
|
||||
.width(Length::Fill)
|
||||
)
|
||||
.padding(15)
|
||||
.on_press(
|
||||
Message::SelectDescriptorTemplate(
|
||||
context::DescriptorTemplate::MultisigSecurity,
|
||||
)
|
||||
).style(theme::Button::Secondary)
|
||||
.width(Length::Fill),
|
||||
)
|
||||
.push(
|
||||
Button::new(
|
||||
Column::new()
|
||||
.align_items(Alignment::Start)
|
||||
.push(h3("Custom (choose your own)"))
|
||||
.push(p2_regular("Create a custom setup that fits all your needs.").style(color::GREY_2))
|
||||
.width(Length::Fill)
|
||||
)
|
||||
.padding(15)
|
||||
.on_press(
|
||||
Message::SelectDescriptorTemplate(
|
||||
context::DescriptorTemplate::Custom,
|
||||
)
|
||||
).style(theme::Button::Secondary)
|
||||
.width(Length::Fill),
|
||||
)
|
||||
.spacing(20),
|
||||
|
||||
@ -0,0 +1,240 @@
|
||||
use iced::{alignment, widget::Space, Alignment, Length};
|
||||
|
||||
use liana_ui::{
|
||||
color,
|
||||
component::{
|
||||
button, collapse,
|
||||
text::{h3, p1_regular, text, Text, H3_SIZE},
|
||||
},
|
||||
icon, image, theme,
|
||||
widget::*,
|
||||
};
|
||||
|
||||
use crate::installer::{
|
||||
context,
|
||||
message::{self, Message},
|
||||
step::descriptor::editor::key::Key,
|
||||
view::{
|
||||
editor::{define_descriptor_advanced_settings, defined_key, path, undefined_key},
|
||||
layout,
|
||||
},
|
||||
};
|
||||
|
||||
pub fn multisig_security_template_description(
|
||||
progress: (usize, usize),
|
||||
) -> Element<'static, Message> {
|
||||
layout(
|
||||
progress,
|
||||
None,
|
||||
"Introduction",
|
||||
Column::new()
|
||||
.align_items(Alignment::Start)
|
||||
.push(h3("Multisig security wallet"))
|
||||
.max_width(800.0)
|
||||
.push(Container::new(
|
||||
p1_regular("For this setup you will need 3 keys: two Primary Keys and a Recovery Key. For security reasons, we suggest you use 3 Hardware Wallets to store them.")
|
||||
.style(color::GREY_2)
|
||||
.horizontal_alignment(alignment::Horizontal::Left)
|
||||
).align_x(alignment::Horizontal::Left).width(Length::Fill))
|
||||
.push(Row::new()
|
||||
.spacing(30)
|
||||
.push(
|
||||
Row::new()
|
||||
.align_items(Alignment::Center)
|
||||
.spacing(10)
|
||||
.push(icon::round_key_icon().size(H3_SIZE).style(color::GREEN))
|
||||
.push(p1_regular("Primary key #1").bold())
|
||||
).push(
|
||||
Row::new()
|
||||
.align_items(Alignment::Center)
|
||||
.spacing(10)
|
||||
.push(icon::round_key_icon().size(H3_SIZE).style(color::GREEN))
|
||||
.push(p1_regular("Primary key #2").bold())
|
||||
).push(
|
||||
Row::new()
|
||||
.align_items(Alignment::Center)
|
||||
.spacing(10)
|
||||
.push(icon::round_key_icon().size(H3_SIZE).style(color::ORANGE))
|
||||
.push(p1_regular("Recovery key").bold())
|
||||
))
|
||||
.push(Container::new(
|
||||
p1_regular("The Primary Keys will compose a 2-of-2 multisig which will be your always-active spending policy. In case one of your keys becomes unavailable, after a period of inactivity you will be able to recover your funds using the Recovery Key together with one of your Primary Keys (2-of-3 multisig):")
|
||||
.style(color::GREY_2)
|
||||
.horizontal_alignment(alignment::Horizontal::Left)
|
||||
).align_x(alignment::Horizontal::Left).width(Length::Fill))
|
||||
.push(image::multisig_security_template_description().width(Length::Fill))
|
||||
.push(Row::new().push(Space::with_width(Length::Fill)).push(button::primary(None, "Select").width(Length::Fixed(200.0)).on_press(Message::Next)))
|
||||
.spacing(20),
|
||||
true,
|
||||
Some(Message::Previous),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn multisig_security_template<'a>(
|
||||
progress: (usize, usize),
|
||||
use_taproot: bool,
|
||||
primary_keys: Vec<Option<&'a Key>>,
|
||||
recovery_keys: Vec<Option<&'a Key>>,
|
||||
sequence: u16,
|
||||
threshold: usize,
|
||||
valid: bool,
|
||||
) -> Element<'a, Message> {
|
||||
layout(
|
||||
progress,
|
||||
None,
|
||||
"Set keys",
|
||||
Column::new()
|
||||
.align_items(Alignment::Start)
|
||||
.max_width(1000.0)
|
||||
.push(collapse::Collapse::new(
|
||||
|| {
|
||||
Button::new(
|
||||
Row::new()
|
||||
.align_items(Alignment::Center)
|
||||
.spacing(10)
|
||||
.push(text("Advanced settings").small().bold())
|
||||
.push(icon::collapse_icon()),
|
||||
)
|
||||
.style(theme::Button::Transparent)
|
||||
},
|
||||
|| {
|
||||
Button::new(
|
||||
Row::new()
|
||||
.align_items(Alignment::Center)
|
||||
.spacing(10)
|
||||
.push(text("Advanced settings").small().bold())
|
||||
.push(icon::collapsed_icon()),
|
||||
)
|
||||
.style(theme::Button::Transparent)
|
||||
},
|
||||
move || define_descriptor_advanced_settings(use_taproot),
|
||||
))
|
||||
.push(
|
||||
path(
|
||||
color::GREEN,
|
||||
None,
|
||||
0,
|
||||
false,
|
||||
primary_keys.len(),
|
||||
primary_keys
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, primary_key)| {
|
||||
if let Some(key) = primary_key {
|
||||
defined_key(
|
||||
&key.name,
|
||||
color::GREEN,
|
||||
format!("Primary key #{}", i + 1),
|
||||
if use_taproot && !key.is_compatible_taproot {
|
||||
Some("Key is not compatible with taproot")
|
||||
} else {
|
||||
None
|
||||
},
|
||||
true,
|
||||
)
|
||||
} else {
|
||||
undefined_key(
|
||||
color::GREEN,
|
||||
format!("Primary key #{}", i + 1),
|
||||
!primary_keys[0..i].iter().any(|k| k.is_none()),
|
||||
true,
|
||||
)
|
||||
}
|
||||
.map(move |msg| message::DefinePath::Key(i, msg))
|
||||
})
|
||||
.collect(),
|
||||
true,
|
||||
)
|
||||
.map(move |msg| {
|
||||
if let message::DefinePath::Key(i, message::DefineKey::Edit) = msg {
|
||||
Message::DefineDescriptor(message::DefineDescriptor::KeysEdit(vec![
|
||||
(0, i),
|
||||
(1, i),
|
||||
]))
|
||||
} else {
|
||||
Message::DefineDescriptor(message::DefineDescriptor::Path(0, msg))
|
||||
}
|
||||
}),
|
||||
)
|
||||
.push(
|
||||
path(
|
||||
color::ORANGE,
|
||||
None,
|
||||
sequence,
|
||||
false,
|
||||
threshold,
|
||||
recovery_keys
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(j, recovery_key)| {
|
||||
if let Some(key) = recovery_key {
|
||||
defined_key(
|
||||
&key.name,
|
||||
if j < 2 { color::GREEN } else { color::ORANGE },
|
||||
if j < 2 {
|
||||
format!("Primary key #{}", j + 1)
|
||||
} else {
|
||||
"Recovery key".to_string()
|
||||
},
|
||||
if use_taproot && !key.is_compatible_taproot {
|
||||
Some("Key is not compatible with Taproot")
|
||||
} else {
|
||||
None
|
||||
},
|
||||
true,
|
||||
)
|
||||
} else {
|
||||
undefined_key(
|
||||
if j < 2 { color::GREEN } else { color::ORANGE },
|
||||
if j < 2 {
|
||||
format!("Primary key #{}", j + 1)
|
||||
} else {
|
||||
"Recovery key".to_string()
|
||||
},
|
||||
!(primary_keys.iter().any(|k| k.is_none())
|
||||
|| recovery_keys[0..j].iter().any(|k| k.is_none())),
|
||||
true,
|
||||
)
|
||||
}
|
||||
.map(move |msg| message::DefinePath::Key(j, msg))
|
||||
})
|
||||
.collect(),
|
||||
true,
|
||||
)
|
||||
.map(move |msg| {
|
||||
if let message::DefinePath::Key(i, message::DefineKey::Edit) = msg {
|
||||
Message::DefineDescriptor(message::DefineDescriptor::KeysEdit(if i < 2 {
|
||||
vec![(0, i), (1, i)]
|
||||
} else {
|
||||
// recovery path is the path with three keys
|
||||
vec![(1, i)]
|
||||
}))
|
||||
} else {
|
||||
Message::DefineDescriptor(message::DefineDescriptor::Path(1, msg))
|
||||
}
|
||||
}),
|
||||
)
|
||||
.push(
|
||||
Row::new()
|
||||
.push(
|
||||
button::secondary(None, "Customize")
|
||||
.width(Length::Fixed(200.0))
|
||||
.on_press(Message::DefineDescriptor(
|
||||
message::DefineDescriptor::ChangeTemplate(
|
||||
context::DescriptorTemplate::Custom,
|
||||
),
|
||||
)),
|
||||
)
|
||||
.push(Space::with_width(Length::Fill))
|
||||
.push(
|
||||
button::primary(None, "Continue")
|
||||
.width(Length::Fixed(200.0))
|
||||
.on_press_maybe(if valid { Some(Message::Next) } else { None }),
|
||||
),
|
||||
)
|
||||
.push(Space::with_height(100.0))
|
||||
.spacing(20),
|
||||
true,
|
||||
Some(Message::Previous),
|
||||
)
|
||||
}
|
||||
@ -75,3 +75,11 @@ pub fn custom_template_description() -> Svg {
|
||||
let h = Handle::from_memory(CUSTOM_TEMPLATE_DESC.to_vec());
|
||||
Svg::new(h)
|
||||
}
|
||||
|
||||
const MULTISIG_SECURITY_TEMPLATE_DESC: &[u8] =
|
||||
include_bytes!("../static/images/multisig_security_template.svg");
|
||||
|
||||
pub fn multisig_security_template_description() -> Svg {
|
||||
let h = Handle::from_memory(MULTISIG_SECURITY_TEMPLATE_DESC.to_vec());
|
||||
Svg::new(h)
|
||||
}
|
||||
|
||||
45
gui/ui/static/images/multisig_security_template.svg
Normal file
45
gui/ui/static/images/multisig_security_template.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 173 KiB |
Loading…
x
Reference in New Issue
Block a user