installer: generate multiple xpubs in participate step

This commit is contained in:
edouard 2023-01-31 10:44:10 +01:00
parent 445ad733fb
commit ca39b15edd
3 changed files with 179 additions and 102 deletions

View File

@ -25,7 +25,7 @@ pub enum Message {
Network(Network),
DefineBitcoind(DefineBitcoind),
DefineDescriptor(DefineDescriptor),
ImportXpub(Result<DescriptorPublicKey, Error>),
ImportXpub(usize, Result<DescriptorPublicKey, Error>),
ConnectedHardwareWallets(Vec<HardwareWallet>),
WalletRegistered(Result<(Fingerprint, Option<[u8; 32]>), Error>),
}

View File

@ -746,18 +746,76 @@ async fn get_extended_pubkey(
}))
}
pub struct HardwareWalletXpubs {
hw: HardwareWallet,
xpubs: Vec<String>,
processing: bool,
error: Option<Error>,
next_account: ChildNumber,
}
impl HardwareWalletXpubs {
fn new(hw: HardwareWallet) -> Self {
Self {
hw,
xpubs: Vec::new(),
processing: false,
error: None,
next_account: ChildNumber::from_hardened_idx(0).unwrap(),
}
}
fn update(&mut self, res: Result<DescriptorPublicKey, Error>) {
self.processing = false;
match res {
Err(e) => {
self.error = e.into();
}
Ok(xpub) => {
self.error = None;
self.next_account = self.next_account.increment().unwrap();
self.xpubs
.push(xpub.to_string().trim_end_matches("/<0;1>/*").to_string());
}
}
}
fn select(&mut self, i: usize, network: Network) -> Command<Message> {
let device = self.hw.device.clone();
self.processing = true;
self.error = None;
let fingerprint = self.hw.fingerprint;
let next_account = self.next_account;
Command::perform(
async move {
(
i,
get_extended_pubkey(device, fingerprint, network, next_account).await,
)
},
|(i, res)| Message::ImportXpub(i, res),
)
}
pub fn view(&self, i: usize) -> Element<Message> {
view::hardware_wallet_xpubs(
i,
&self.xpubs,
&self.hw,
self.processing,
self.error.as_ref(),
)
}
}
pub struct ParticipateXpub {
network: Network,
network_valid: bool,
data_dir: Option<PathBuf>,
xpub: Option<String>,
shared: bool,
processing: bool,
chosen_hw: Option<usize>,
hws: Vec<(HardwareWallet, bool)>,
error: Option<Error>,
xpubs_hw: Vec<HardwareWalletXpubs>,
}
impl ParticipateXpub {
@ -766,12 +824,8 @@ impl ParticipateXpub {
network: Network::Bitcoin,
network_valid: true,
data_dir: None,
processing: false,
xpub: None,
chosen_hw: None,
hws: Vec::new(),
xpubs_hw: Vec::new(),
shared: false,
error: None,
}
}
}
@ -788,48 +842,26 @@ impl Step for ParticipateXpub {
self.network_valid = !network_datadir.exists();
}
Message::UserActionDone(shared) => self.shared = shared,
Message::ImportXpub(res) => {
self.processing = false;
match res {
Err(e) => {
self.error = e.into();
self.chosen_hw = None;
}
Ok(xpub) => {
self.error = None;
self.xpub = Some(xpub.to_string().trim_end_matches("/<0;1>/*").to_string());
for (i, (_, imported)) in self.hws.iter_mut().enumerate() {
*imported = Some(i) == self.chosen_hw;
}
self.chosen_hw = None;
}
Message::ImportXpub(i, res) => {
if let Some(hw) = self.xpubs_hw.get_mut(i) {
hw.update(res);
}
}
Message::Select(i) => {
if let Some((hw, _)) = self.hws.get(i) {
let device = hw.device.clone();
self.chosen_hw = Some(i);
self.processing = true;
self.error = None;
return Command::perform(
get_extended_pubkey(
device,
hw.fingerprint,
self.network,
ChildNumber::from_hardened_idx(0).unwrap(),
),
Message::ImportXpub,
);
if let Some(hw) = self.xpubs_hw.get_mut(i) {
return hw.select(i, self.network);
}
}
Message::ConnectedHardwareWallets(hws) => {
for hw in hws {
if !self
.hws
.iter()
.any(|(h, _)| h.fingerprint == hw.fingerprint)
if let Some(xpub_hw) = self
.xpubs_hw
.iter_mut()
.find(|h| h.hw.fingerprint == hw.fingerprint)
{
self.hws.push((hw, false));
xpub_hw.hw = hw;
} else {
self.xpubs_hw.push(HardwareWalletXpubs::new(hw));
}
}
}
@ -866,12 +898,12 @@ impl Step for ParticipateXpub {
progress,
self.network,
self.network_valid,
&self.hws,
self.processing,
self.chosen_hw,
self.xpub.as_ref(),
self.xpubs_hw
.iter()
.enumerate()
.map(|(i, hw)| hw.view(i))
.collect(),
self.shared,
self.error.as_ref(),
)
}
}

View File

@ -409,18 +409,103 @@ pub fn import_descriptor<'a>(
)
}
#[allow(clippy::too_many_arguments)]
pub fn participate_xpub<'a>(
pub fn hardware_wallet_xpubs<'a>(
i: usize,
xpubs: &'a Vec<String>,
hw: &HardwareWallet,
processing: bool,
error: Option<&Error>,
) -> Element<'a, Message> {
let mut bttn = Button::new(
Row::new()
.align_items(Alignment::Center)
.push(
Column::new()
.push(text(format!("{}", hw.kind)).bold())
.push(text(format!("fingerprint: {}", hw.fingerprint)).small())
.spacing(5)
.width(Length::Fill),
)
.push_maybe(error.map(|e| {
iced::widget::tooltip(
Row::new()
.spacing(5)
.align_items(Alignment::Center)
.push(icon::warning_icon().style(color::ALERT))
.push(text("An error occured").style(color::ALERT)),
e,
iced::widget::tooltip::Position::Bottom,
)
.style(card::ErrorCardStyle)
})),
)
.padding(10)
.style(button::Style::TransparentBorder.into())
.width(Length::Fill);
if !processing {
bttn = bttn.on_press(Message::Select(i));
}
Container::new(
Column::new()
.push(bttn)
.push_maybe(if xpubs.is_empty() {
None
} else {
Some(separation().width(Length::Fill))
})
.push_maybe(if xpubs.is_empty() {
None
} else {
Some(xpubs.iter().fold(Column::new().padding(15), |col, xpub| {
col.push(
Row::new()
.spacing(5)
.align_items(Alignment::Center)
.push(
Container::new(
Scrollable::new(Container::new(text(xpub).small()).padding(10))
.horizontal_scroll(
Properties::new().width(2).scroller_width(2),
),
)
.width(Length::Fill),
)
.push(
Container::new(
button::border(Some(icon::clipboard_icon()), "Copy")
.on_press(Message::Clibpboard(xpub.clone()))
.width(Length::Shrink),
)
.padding(10),
),
)
}))
})
.push_maybe(if !xpubs.is_empty() {
Some(
Container::new(if !processing {
button::border(Some(icon::plus_icon()), "New public key")
.on_press(Message::Select(i))
} else {
button::border(Some(icon::plus_icon()), "New public key")
})
.padding(10),
)
} else {
None
}),
)
.style(card::SimpleCardStyle)
.into()
}
pub fn participate_xpub(
progress: (usize, usize),
network: bitcoin::Network,
network_valid: bool,
hws: &[(HardwareWallet, bool)],
processing: bool,
chosen_hw: Option<usize>,
xpub: Option<&'a String>,
hws: Vec<Element<Message>>,
shared: bool,
error: Option<&Error>,
) -> Element<'a, Message> {
) -> Element<Message> {
let row_network = Row::new()
.spacing(10)
.align_items(Alignment::Center)
@ -442,7 +527,7 @@ pub fn participate_xpub<'a>(
layout(
progress,
Column::new()
.push(text("Share your public key").bold().size(50))
.push(text("Share your public keys").bold().size(50))
.push(
Column::new()
.spacing(20)
@ -456,7 +541,7 @@ pub fn participate_xpub<'a>(
.spacing(10)
.align_items(Alignment::Center)
.push(
Container::new(text("Select your hardware wallet:").bold())
Container::new(text("Generate an extended public key by selecting a signing device:").bold())
.width(Length::Fill),
)
.push(
@ -465,50 +550,11 @@ pub fn participate_xpub<'a>(
),
)
.spacing(10)
.push(
hws.iter()
.enumerate()
.fold(Column::new().spacing(10), |col, (i, hw)| {
col.push(hw_list_view(
i,
&hw.0,
Some(i) == chosen_hw,
processing,
hw.1,
))
}),
)
.push(Column::with_children(hws).spacing(10))
.width(Length::Fill),
)
.push_maybe(xpub.map(|xpub| {
Column::new()
.spacing(5)
.push(text("Your extended pubkey:").bold())
.push(
Row::new()
.spacing(5)
.align_items(Alignment::Center)
.push(
Container::new(
Scrollable::new(Container::new(text(xpub)).padding(10))
.horizontal_scroll(
Properties::new().width(2).scroller_width(2),
),
)
.width(Length::Fill),
)
.push(
Container::new(
button::border(Some(icon::clipboard_icon()), "Copy")
.on_press(Message::Clibpboard(xpub.clone()))
.width(Length::Shrink),
)
.padding(10),
),
)
}))
.push(Checkbox::new(
"I have shared my xpub",
"I have shared my public keys",
shared,
Message::UserActionDone,
))
@ -519,7 +565,6 @@ pub fn participate_xpub<'a>(
} else {
button::primary(None, "Next").width(Length::Units(200))
})
.push_maybe(error.map(|e| card::error("Hardware error", e.to_string())))
.width(Length::Fill)
.height(Length::Fill)
.padding(100)