Merge #986: Gui taproot

b7f35c03300710c71f5e8a4af471260f0a35ddc0 Add installer dropdown for advanced settings (edouardparis)
59a4b181c1e1be64ed8122edd95a7378ebef39af fix: merge tap_script_sigs from signed psbt (edouardparis)
02a52b98bcdbcf62d42c748b154beaee76d0f26a add ledger version support for tapminiscript (edouardparis)
2debb32181f2365fb4e30d9d7e28a4cb7c86cd65 Add taproot support to installer descriptor editor step (edouardparis)
8bc0cac00a4894c316285223924e91a85f1c6088 gui: async-hwi:0.0.16 (edouardparis)
4a4c78d5f7ed43aae1199481fb060e77d1d88c80 bump liana:master (edouardparis)

Pull request description:

  based on #985

ACKs for top commit:
  darosior:
    tACK b7f35c03300710c71f5e8a4af471260f0a35ddc0 -- i've lightly tested this a couple times. It's good enough to get in and get tested along with the other changes.

Tree-SHA512: 4481be4797cf6fa901de9fec989837381c7817d18dd2c9a45ff1802a15982d1251696e577fb23da2d4ca9f0ed27044dade28f0e7b56703594dae4b3a5065306b
This commit is contained in:
Antoine Poinsot 2024-03-20 19:19:49 +01:00
commit 4f78d3b3d0
No known key found for this signature in database
GPG Key ID: E13FC145CD3F4304
12 changed files with 338 additions and 117 deletions

92
gui/Cargo.lock generated
View File

@ -77,22 +77,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac1f845298e95f983ff1944b728ae08b8cebab80d684f0a832ed0fc74dfa27e2"
dependencies = [
"cfg-if",
"cipher 0.4.4",
"cipher",
"cpufeatures",
]
[[package]]
name = "aes-ctr"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7729c3cde54d67063be556aeac75a81330d802f0259500ca40cb52967f975763"
dependencies = [
"aes-soft",
"aesni",
"cipher 0.2.5",
"ctr 0.6.0",
]
[[package]]
name = "aes-gcm"
version = "0.10.3"
@ -101,32 +89,12 @@ checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1"
dependencies = [
"aead",
"aes",
"cipher 0.4.4",
"ctr 0.9.2",
"cipher",
"ctr",
"ghash",
"subtle",
]
[[package]]
name = "aes-soft"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be14c7498ea50828a38d0e24a765ed2effe92a705885b57d029cd67d45744072"
dependencies = [
"cipher 0.2.5",
"opaque-debug",
]
[[package]]
name = "aesni"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea2e11f5e94c2f7d386164cc2aa1f97823fed6f259e486940a71c174dd01b0ce"
dependencies = [
"cipher 0.2.5",
"opaque-debug",
]
[[package]]
name = "ahash"
version = "0.7.6"
@ -224,9 +192,9 @@ dependencies = [
[[package]]
name = "async-hwi"
version = "0.0.14"
version = "0.0.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38b9ba5d4bd59fea79a70a375da2fe077b2b17ce26ace805d6939275719ad66e"
checksum = "912663643d018301fb5534949e8430515c7cdc9bf453bfefba2dc70dc854dd31"
dependencies = [
"async-trait",
"bitbox-api",
@ -585,7 +553,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818"
dependencies = [
"cfg-if",
"cipher 0.4.4",
"cipher",
"cpufeatures",
]
@ -597,7 +565,7 @@ checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35"
dependencies = [
"aead",
"chacha20",
"cipher 0.4.4",
"cipher",
"poly1305",
"zeroize",
]
@ -623,15 +591,6 @@ dependencies = [
"winapi",
]
[[package]]
name = "cipher"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12f8e7987cbd042a63249497f41aed09f8e65add917ea6566effbc56578d6801"
dependencies = [
"generic-array",
]
[[package]]
name = "cipher"
version = "0.4.4"
@ -736,13 +695,14 @@ dependencies = [
[[package]]
name = "coldcard"
version = "0.11.0"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a02245612d550816be0d18aaea125372568a2d3d2f673f7b313294543f0fe03d"
checksum = "53b201b4f71707e6330445ed5b6af1024904c6d2c05aca2f74e5fa51d2e56a0f"
dependencies = [
"aes-ctr",
"aes",
"base58",
"bitcoin_hashes 0.12.0",
"bitcoin_hashes 0.13.0",
"ctr",
"hidapi",
"k256",
"rand",
@ -968,22 +928,13 @@ dependencies = [
"typenum",
]
[[package]]
name = "ctr"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb4a30d54f7443bf3d6191dcd486aca19e67cb3c49fa7a06a319966346707e7f"
dependencies = [
"cipher 0.2.5",
]
[[package]]
name = "ctr"
version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835"
dependencies = [
"cipher 0.4.4",
"cipher",
]
[[package]]
@ -2105,14 +2056,15 @@ checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df"
[[package]]
name = "hidapi"
version = "2.4.1"
version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "723777263b0dcc5730aec947496bd8c3940ba63c15f5633b288cc615f4f6af79"
checksum = "9a722fb137d008dbf264f54612457f8eb6a299efbcb0138178964a0809035d74"
dependencies = [
"cc",
"cfg-if",
"libc",
"pkg-config",
"winapi",
"windows-sys 0.48.0",
]
[[package]]
@ -2549,9 +2501,9 @@ dependencies = [
[[package]]
name = "k256"
version = "0.13.1"
version = "0.13.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cadb76004ed8e97623117f3df85b17aaa6626ab0b0831e6573f104df16cd1bcc"
checksum = "956ff9b67e26e1a6a866cb758f12c6f8746208489e3e4a4b5580802f2f0a587b"
dependencies = [
"cfg-if",
"ecdsa",
@ -2656,9 +2608,9 @@ dependencies = [
[[package]]
name = "ledger_bitcoin_client"
version = "0.4.0"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8946cd76087649eed46851965dd5d06c45b996b6cfbe4436640e0fa6b7c4766f"
checksum = "8606a9c7375fb139e68fc1ca7cf9c6709566eeca448ff33e37632d8a4302eefe"
dependencies = [
"async-trait",
"bitcoin",
@ -2668,7 +2620,7 @@ dependencies = [
[[package]]
name = "liana"
version = "4.0.0"
source = "git+https://github.com/wizardsardine/liana?branch=master#16afa3e9925cd016db03dbe954403bfa348b89e7"
source = "git+https://github.com/wizardsardine/liana?branch=master#dd29578c500ea7644154a5923bea9fa1db9b68e5"
dependencies = [
"backtrace",
"bdk_coin_select",

View File

@ -14,7 +14,7 @@ name = "liana-gui"
path = "src/main.rs"
[dependencies]
async-hwi = "0.0.14"
async-hwi = "0.0.16"
liana = { git = "https://github.com/wizardsardine/liana", branch = "master", default-features = false, features = ["nonblocking_shutdown"] }
liana_ui = { path = "ui" }
backtrace = "0.3"

View File

@ -574,6 +574,12 @@ fn merge_signatures(psbt: &mut Psbt, signed_psbt: &Psbt) {
psbtin
.partial_sigs
.extend(&mut signed_psbtin.partial_sigs.iter());
psbtin
.tap_script_sigs
.extend(&mut signed_psbtin.tap_script_sigs.iter());
if let Some(sig) = signed_psbtin.tap_key_sig {
psbtin.tap_key_sig = Some(sig);
}
}
}

View File

@ -33,11 +33,12 @@ pub fn hw_list_view(
alias.as_ref(),
)
} else if *registered == Some(false) {
hw::unregistered_hardware_wallet(
hw::warning_hardware_wallet(
kind,
version.as_ref(),
fingerprint,
alias.as_ref(),
"The wallet descriptor is not registered on the device.\n You can register it in the settings.",
)
} else {
hw::supported_hardware_wallet(kind, version.as_ref(), fingerprint, alias.as_ref())

View File

@ -673,3 +673,32 @@ fn ledger_version_supported(version: Option<&Version>) -> bool {
false
}
}
// Kind and minimal version of devices supporting tapminiscript.
// We cannot use a lazy_static HashMap yet, because DeviceKind does not implement Hash.
const DEVICES_COMPATIBLE_WITH_TAPMINISCRIPT: [(DeviceKind, Option<Version>); 1] = [(
DeviceKind::Ledger,
Some(Version {
major: 2,
minor: 2,
patch: 0,
prerelease: None,
}),
)];
pub fn is_compatible_with_tapminiscript(
device_kind: &DeviceKind,
version: Option<&Version>,
) -> bool {
DEVICES_COMPATIBLE_WITH_TAPMINISCRIPT
.iter()
.any(|(kind, minimal_version)| {
device_kind == kind
&& match (version, minimal_version) {
(Some(v1), Some(v2)) => v1 >= v2,
(None, Some(_)) => false,
(Some(_), None) => true,
(None, None) => true,
}
})
}

View File

@ -10,7 +10,7 @@ use crate::{
download::Progress,
hw::HardwareWalletMessage,
};
use async_hwi::DeviceKind;
use async_hwi::{DeviceKind, Version};
#[derive(Debug, Clone)]
pub enum Message {
@ -30,6 +30,7 @@ pub enum Message {
UseHotSigner,
Installed(Result<PathBuf, Error>),
Network(Network),
CreateTaprootDescriptor(bool),
SelectBitcoindType(SelectBitcoindTypeMsg),
InternalBitcoind(InternalBitcoindMsg),
DefineBitcoind(DefineBitcoind),
@ -85,12 +86,18 @@ pub enum DefinePath {
EditSequence,
}
#[allow(clippy::large_enum_variant)]
#[derive(Debug, Clone)]
pub enum DefineKey {
Delete,
Edit,
Clipboard(String),
Edited(String, DescriptorPublicKey, Option<DeviceKind>),
Edited(
String,
DescriptorPublicKey,
Option<DeviceKind>,
Option<Version>,
),
}
#[derive(Debug, Clone)]

View File

@ -24,8 +24,9 @@ use liana_ui::{
widget::Element,
};
use async_hwi::DeviceKind;
use async_hwi::{DeviceKind, Version};
use crate::hw;
use crate::{
app::{settings::KeySetting, wallet::wallet_name},
hw::{HardwareWallet, HardwareWallets},
@ -72,6 +73,7 @@ impl RecoveryPath {
&self,
aliases: &HashMap<Fingerprint, String>,
duplicate_name: &HashSet<Fingerprint>,
incompatible_with_tapminiscript: &HashSet<Fingerprint>,
) -> Element<message::DefinePath> {
view::recovery_path_view(
self.sequence,
@ -85,6 +87,7 @@ impl RecoveryPath {
view::defined_descriptor_key(
aliases.get(key).unwrap().to_string(),
duplicate_name.contains(key),
incompatible_with_tapminiscript.contains(key),
)
} else {
view::undefined_descriptor_key()
@ -99,6 +102,7 @@ impl RecoveryPath {
struct Setup {
keys: Vec<Key>,
duplicate_name: HashSet<Fingerprint>,
incompatible_with_tapminiscript: HashSet<Fingerprint>,
spending_keys: Vec<Option<Fingerprint>>,
spending_threshold: usize,
recovery_paths: Vec<RecoveryPath>,
@ -109,6 +113,7 @@ impl Setup {
Self {
keys: Vec::new(),
duplicate_name: HashSet::new(),
incompatible_with_tapminiscript: HashSet::new(),
spending_keys: vec![None],
spending_threshold: 1,
recovery_paths: vec![RecoveryPath::new()],
@ -120,6 +125,7 @@ impl Setup {
&& !self.spending_keys.iter().any(|k| k.is_none())
&& !self.recovery_paths.iter().any(|path| !path.valid())
&& self.duplicate_name.is_empty()
&& self.incompatible_with_tapminiscript.is_empty()
}
// Mark as duplicate every defined key that have the same name but not the same fingerprint.
@ -150,6 +156,33 @@ impl Setup {
}
}
fn check_for_tapminiscript_support(&mut self, must_support_taproot: bool) {
self.incompatible_with_tapminiscript = HashSet::new();
if must_support_taproot {
for key in &self.keys {
// check if key is used by a path
if !self
.spending_keys
.iter()
.chain(self.recovery_paths.iter().flat_map(|path| &path.keys))
.any(|k| *k == Some(key.fingerprint))
{
continue;
}
// device_kind is none only for HotSigner which is compatible.
if let Some(device_kind) = key.device_kind.as_ref() {
if !hw::is_compatible_with_tapminiscript(
device_kind,
key.device_version.as_ref(),
) {
self.incompatible_with_tapminiscript.insert(key.fingerprint);
}
}
}
}
}
fn keys_aliases(&self) -> HashMap<Fingerprint, String> {
let mut map = HashMap::new();
for key in &self.keys {
@ -160,11 +193,13 @@ impl Setup {
}
pub struct DefineDescriptor {
network: Network,
network_valid: bool,
data_dir: Option<PathBuf>,
setup: HashMap<Network, Setup>,
network: Network,
network_valid: bool,
use_taproot: bool,
modal: Option<Box<dyn DescriptorEditModal>>,
signer: Arc<Mutex<Signer>>,
@ -175,10 +210,10 @@ impl DefineDescriptor {
pub fn new(signer: Arc<Mutex<Signer>>) -> Self {
Self {
network: Network::Bitcoin,
use_taproot: false,
setup: HashMap::from([(Network::Bitcoin, Setup::new())]),
data_dir: None,
network_valid: true,
modal: None,
signer,
error: None,
@ -204,6 +239,14 @@ impl DefineDescriptor {
network_datadir.push(self.network.to_string());
self.network_valid = !network_datadir.exists();
}
self.check_setup()
}
fn check_setup(&mut self) {
self.setup_mut().check_for_duplicate();
let use_taproot = self.use_taproot;
self.setup_mut()
.check_for_tapminiscript_support(use_taproot);
}
}
@ -221,6 +264,10 @@ impl Step for DefineDescriptor {
hws.set_network(network);
self.set_network(network)
}
Message::CreateTaprootDescriptor(use_taproot) => {
self.use_taproot = use_taproot;
self.check_setup();
}
Message::DefineDescriptor(message::DefineDescriptor::AddRecoveryPath) => {
self.setup_mut().recovery_paths.push(RecoveryPath::new());
}
@ -236,7 +283,7 @@ impl Step for DefineDescriptor {
message::DefineKey::Clipboard(key) => {
return Command::perform(async move { key }, Message::Clibpboard);
}
message::DefineKey::Edited(name, imported_key, kind) => {
message::DefineKey::Edited(name, imported_key, kind, version) => {
let fingerprint = imported_key.master_fingerprint();
hws.set_alias(fingerprint, name.clone());
if let Some(key) = self
@ -252,17 +299,20 @@ impl Step for DefineDescriptor {
name,
key: imported_key,
device_kind: kind,
device_version: version,
});
}
self.setup_mut().spending_keys[i] = Some(fingerprint);
self.modal = None;
self.setup_mut().check_for_duplicate();
self.check_setup();
}
message::DefineKey::Edit => {
let use_taproot = self.use_taproot;
let setup = self.setup_mut();
let modal = EditXpubModal::new(
use_taproot,
HashSet::from_iter(setup.spending_keys.iter().filter_map(|key| {
if key.is_some() && key != &setup.spending_keys[i] {
*key
@ -293,7 +343,7 @@ impl Step for DefineDescriptor {
{
self.setup_mut().spending_threshold -= 1;
}
self.setup_mut().check_for_duplicate();
self.check_setup();
}
},
_ => {}
@ -327,7 +377,7 @@ impl Step for DefineDescriptor {
message::DefineKey::Clipboard(key) => {
return Command::perform(async move { key }, Message::Clibpboard);
}
message::DefineKey::Edited(name, imported_key, kind) => {
message::DefineKey::Edited(name, imported_key, kind, version) => {
let fingerprint = imported_key.master_fingerprint();
hws.set_alias(fingerprint, name.clone());
if let Some(key) = self
@ -343,17 +393,20 @@ impl Step for DefineDescriptor {
name,
key: imported_key,
device_kind: kind,
device_version: version,
});
}
self.setup_mut().recovery_paths[i].keys[j] = Some(fingerprint);
self.modal = None;
self.setup_mut().check_for_duplicate();
self.check_setup();
}
message::DefineKey::Edit => {
let use_taproot = self.use_taproot;
let setup = self.setup_mut();
let modal = EditXpubModal::new(
use_taproot,
HashSet::from_iter(setup.recovery_paths[i].keys.iter().filter_map(
|key| {
if key.is_some() && key != &setup.recovery_paths[i].keys[j] {
@ -390,7 +443,7 @@ impl Step for DefineDescriptor {
{
self.setup_mut().recovery_paths.remove(i);
}
self.setup_mut().check_for_duplicate();
self.check_setup();
}
},
},
@ -490,7 +543,11 @@ impl Step for DefineDescriptor {
PathInfo::Multi(self.setup[&self.network].spending_threshold, spending_keys)
};
let policy = match LianaPolicy::new(spending_keys, recovery_paths) {
let policy = match if self.use_taproot {
LianaPolicy::new(spending_keys, recovery_paths)
} else {
LianaPolicy::new_legacy(spending_keys, recovery_paths)
} {
Ok(policy) => policy,
Err(e) => {
self.error = Some(e.to_string());
@ -513,6 +570,7 @@ impl Step for DefineDescriptor {
progress,
self.network,
self.network_valid,
self.use_taproot,
self.setup[&self.network]
.spending_keys
.iter()
@ -522,6 +580,9 @@ impl Step for DefineDescriptor {
view::defined_descriptor_key(
aliases.get(key).unwrap().to_string(),
self.setup[&self.network].duplicate_name.contains(key),
self.setup[&self.network]
.incompatible_with_tapminiscript
.contains(key),
)
} else {
view::undefined_descriptor_key()
@ -539,12 +600,14 @@ impl Step for DefineDescriptor {
.iter()
.enumerate()
.map(|(i, path)| {
path.view(&aliases, &self.setup[&self.network].duplicate_name)
.map(move |msg| {
Message::DefineDescriptor(message::DefineDescriptor::RecoveryPath(
i, msg,
))
})
path.view(
&aliases,
&self.setup[&self.network].duplicate_name,
&self.setup[&self.network].incompatible_with_tapminiscript,
)
.map(move |msg| {
Message::DefineDescriptor(message::DefineDescriptor::RecoveryPath(i, msg))
})
})
.collect(),
self.valid(),
@ -583,6 +646,7 @@ fn new_multixkey_from_xpub(
#[derive(Clone)]
pub struct Key {
pub device_kind: Option<DeviceKind>,
pub device_version: Option<Version>,
pub name: String,
pub fingerprint: Fingerprint,
pub key: DescriptorPublicKey,
@ -675,6 +739,7 @@ impl DescriptorEditModal for EditSequenceModal {
}
pub struct EditXpubModal {
device_must_support_tapminiscript: bool,
/// None if path is primary path
path_index: Option<usize>,
key_index: usize,
@ -692,12 +757,13 @@ pub struct EditXpubModal {
keys: Vec<Key>,
hot_signer: Arc<Mutex<Signer>>,
hot_signer_fingerprint: Fingerprint,
chosen_signer: Option<(Fingerprint, Option<DeviceKind>)>,
chosen_signer: Option<(Fingerprint, Option<DeviceKind>, Option<Version>)>,
}
impl EditXpubModal {
#[allow(clippy::too_many_arguments)]
fn new(
device_must_support_tapminiscript: bool,
other_path_keys: HashSet<Fingerprint>,
key: Option<Fingerprint>,
path_index: Option<usize>,
@ -708,6 +774,7 @@ impl EditXpubModal {
) -> Self {
let hot_signer_fingerprint = hot_signer.lock().unwrap().fingerprint();
Self {
device_must_support_tapminiscript,
other_path_keys,
form_name: form::Value {
valid: true,
@ -740,7 +807,7 @@ impl EditXpubModal {
error: None,
network,
edit_name: false,
chosen_signer: key.map(|k| (k, None)),
chosen_signer: key.map(|k| (k, None, None)),
hot_signer_fingerprint,
hot_signer,
duplicate_master_fg: false,
@ -767,10 +834,11 @@ impl DescriptorEditModal for EditXpubModal {
device,
fingerprint,
kind,
version,
..
}) = hws.list.get(i)
{
self.chosen_signer = Some((*fingerprint, Some(*kind)));
self.chosen_signer = Some((*fingerprint, Some(*kind), version.clone()));
self.processing = true;
return Command::perform(
get_extended_pubkey(device.clone(), *fingerprint, self.network),
@ -787,7 +855,7 @@ impl DescriptorEditModal for EditXpubModal {
}
Message::UseHotSigner => {
let fingerprint = self.hot_signer.lock().unwrap().fingerprint();
self.chosen_signer = Some((fingerprint, None));
self.chosen_signer = Some((fingerprint, None, None));
self.form_xpub.valid = true;
if let Some(alias) = self
.keys
@ -882,7 +950,12 @@ impl DescriptorEditModal for EditXpubModal {
if let Ok(key) = DescriptorPublicKey::from_str(&self.form_xpub.value) {
let key_index = self.key_index;
let name = self.form_name.value.clone();
let device_kind = self.chosen_signer.and_then(|(_, kind)| kind);
let (device_kind, device_version) =
if let Some((_, kind, version)) = &self.chosen_signer {
(*kind, version.clone())
} else {
(None, None)
};
if self.other_path_keys.contains(&key.master_fingerprint()) {
self.duplicate_master_fg = true;
} else if let Some(path_index) = self.path_index {
@ -893,7 +966,12 @@ impl DescriptorEditModal for EditXpubModal {
path_index,
message::DefinePath::Key(
key_index,
message::DefineKey::Edited(name, key, device_kind),
message::DefineKey::Edited(
name,
key,
device_kind,
device_version,
),
),
)
},
@ -906,7 +984,12 @@ impl DescriptorEditModal for EditXpubModal {
message::DefineDescriptor::PrimaryPath(
message::DefinePath::Key(
key_index,
message::DefineKey::Edited(name, key, device_kind),
message::DefineKey::Edited(
name,
key,
device_kind,
device_version,
),
),
)
},
@ -917,7 +1000,8 @@ impl DescriptorEditModal for EditXpubModal {
}
message::ImportKeyModal::SelectKey(i) => {
if let Some(key) = self.keys.get(i) {
self.chosen_signer = Some((key.fingerprint, key.device_kind));
self.chosen_signer =
Some((key.fingerprint, key.device_kind, key.device_version.clone()));
self.form_xpub.value = key.key.to_string();
self.form_xpub.valid = true;
self.form_name.value = key.name.clone();
@ -930,7 +1014,7 @@ impl DescriptorEditModal for EditXpubModal {
Command::none()
}
fn view<'a>(&'a self, hws: &'a HardwareWallets) -> Element<'a, Message> {
let chosen_signer = self.chosen_signer.map(|s| s.0);
let chosen_signer = self.chosen_signer.as_ref().map(|s| s.0);
view::edit_key_modal(
self.network,
hws.list
@ -953,6 +1037,7 @@ impl DescriptorEditModal for EditXpubModal {
&& hw.fingerprint() == chosen_signer
&& self.form_xpub.valid
&& !self.form_xpub.value.is_empty(),
self.device_must_support_tapminiscript,
))
}
})
@ -969,13 +1054,15 @@ impl DescriptorEditModal for EditXpubModal {
&key.name,
&key.fingerprint,
key.device_kind.as_ref(),
key.device_version.as_ref(),
Some(key.fingerprint) == chosen_signer,
self.device_must_support_tapminiscript,
))
}
})
.collect(),
self.error.as_ref(),
self.chosen_signer.map(|s| s.0),
self.chosen_signer.as_ref().map(|s| s.0),
&self.hot_signer_fingerprint,
self.keys.iter().find_map(|k| {
if k.fingerprint == self.hot_signer_fingerprint {
@ -1668,6 +1755,7 @@ mod tests {
"My Specter key".to_string(),
DescriptorPublicKey::from_str("[4df3f0e3/84'/0'/0']tpubDDRs9DnRUiJc4hq92PSJKhfzQBgHJUrDo7T2i48smsDfLsQcm3Vh7JhuGqJv8zozVkNFin8YPgpmn2NWNmpRaE3GW2pSxbmAzYf2juy7LeW").unwrap(),
Some(DeviceKind::Specter),
None,
),
);

View File

@ -23,7 +23,7 @@ use liana_ui::{
use crate::{
bitcoind::{ConfigField, RpcAuthType, RpcAuthValues, StartInternalBitcoindError},
hw::HardwareWallet,
hw::{is_compatible_with_tapminiscript, HardwareWallet},
installer::{
message::{self, Message},
prompt,
@ -173,16 +173,28 @@ pub fn welcome<'a>() -> Element<'a, Message> {
.into()
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DescriptorKind {
P2WSH,
Taproot,
}
const DESCRIPTOR_KINDS: [DescriptorKind; 2] = [DescriptorKind::P2WSH, DescriptorKind::Taproot];
impl std::fmt::Display for DescriptorKind {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
Self::P2WSH => write!(f, "P2WSH"),
Self::Taproot => write!(f, "Taproot"),
}
}
}
#[allow(clippy::too_many_arguments)]
pub fn define_descriptor<'a>(
progress: (usize, usize),
pub fn define_descriptor_advanced_settings<'a>(
network: bitcoin::Network,
network_valid: bool,
spending_keys: Vec<Element<'a, Message>>,
spending_threshold: usize,
recovery_paths: Vec<Element<'a, Message>>,
valid: bool,
error: Option<&String>,
use_taproot: bool,
) -> Element<'a, Message> {
let col_network = Column::new()
.spacing(10)
@ -192,7 +204,7 @@ pub fn define_descriptor<'a>(
Message::Network(net.into())
})
.style(if network_valid {
theme::PickList::Simple
theme::PickList::Secondary
} else {
theme::PickList::Invalid
})
@ -204,6 +216,58 @@ pub fn define_descriptor<'a>(
Some(text("A data directory already exists for this network").style(color::RED))
});
let col_wallet = Column::new()
.spacing(10)
.push(text("Descriptor type").bold())
.push(container(
pick_list(
&DESCRIPTOR_KINDS[..],
Some(if use_taproot {
DescriptorKind::Taproot
} else {
DescriptorKind::P2WSH
}),
|kind| Message::CreateTaprootDescriptor(kind == DescriptorKind::Taproot),
)
.style(theme::PickList::Secondary)
.padding(10),
));
container(
Column::new()
.spacing(20)
.push(Space::with_height(0))
.push(separation().width(500))
.push(
Row::new()
.push(col_network)
.push(Space::with_width(100))
.push(col_wallet),
)
.push_maybe(if use_taproot {
Some(
p1_regular("Taproot is only supported by Liana version 5.0 and above")
.style(color::GREY_2),
)
} else {
None
}),
)
.into()
}
#[allow(clippy::too_many_arguments)]
pub fn define_descriptor<'a>(
progress: (usize, usize),
network: bitcoin::Network,
network_valid: bool,
use_taproot: bool,
spending_keys: Vec<Element<'a, Message>>,
spending_threshold: usize,
recovery_paths: Vec<Element<'a, Message>>,
valid: bool,
error: Option<&String>,
) -> Element<'a, Message> {
let col_spending_keys = Column::new()
.push(
Row::new()
@ -263,10 +327,32 @@ pub fn define_descriptor<'a>(
progress,
"Create the wallet",
Column::new()
.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(network, network_valid, use_taproot),
))
.push(
Column::new()
.width(Length::Fill)
.push(col_network)
.push(
Column::new()
.spacing(25)
@ -707,6 +793,7 @@ pub fn register_descriptor<'a>(
hw.fingerprint()
.map(|fg| registered.contains(&fg))
.unwrap_or(false),
false,
))
}),
)
@ -1287,6 +1374,7 @@ pub fn undefined_descriptor_key<'a>() -> Element<'a, message::DefineKey> {
pub fn defined_descriptor_key<'a>(
name: String,
duplicate_name: bool,
incompatible_with_tapminiscript: bool,
) -> Element<'a, message::DefineKey> {
let col = Column::new()
.width(Length::Fill)
@ -1336,6 +1424,21 @@ pub fn defined_descriptor_key<'a>(
)
.push(text("Duplicate name").small().style(color::RED))
.into()
} else if incompatible_with_tapminiscript {
Column::new()
.align_items(Alignment::Center)
.push(
card::invalid(col)
.padding(5)
.height(Length::Fixed(150.0))
.width(Length::Fixed(150.0)),
)
.push(
text("Taproot is not supported\nby this key device")
.small()
.style(color::RED),
)
.into()
} else {
card::simple(col)
.padding(5)
@ -1594,6 +1697,7 @@ pub fn hw_list_view(
chosen: bool,
processing: bool,
selected: bool,
device_must_support_taproot: bool,
) -> Element<Message> {
let mut bttn = Button::new(match hw {
HardwareWallet::Supported {
@ -1607,6 +1711,16 @@ pub fn hw_list_view(
hw::processing_hardware_wallet(kind, version.as_ref(), fingerprint, alias.as_ref())
} else if selected {
hw::selected_hardware_wallet(kind, version.as_ref(), fingerprint, alias.as_ref())
} else if device_must_support_taproot
&& !is_compatible_with_tapminiscript(kind, version.as_ref())
{
hw::warning_hardware_wallet(
kind,
version.as_ref(),
fingerprint,
alias.as_ref(),
"Device firmware version does not support taproot miniscript",
)
} else {
hw::supported_hardware_wallet(kind, version.as_ref(), fingerprint, alias.as_ref())
}
@ -1634,19 +1748,31 @@ pub fn key_list_view<'a>(
name: &'a str,
fingerprint: &'a Fingerprint,
kind: Option<&'a DeviceKind>,
version: Option<&'a async_hwi::Version>,
chosen: bool,
device_must_support_taproot: bool,
) -> Element<'a, Message> {
let bttn = Button::new(if chosen {
hw::selected_hardware_wallet(
kind.map(|k| k.to_string()).unwrap_or_default(),
None::<String>,
version,
fingerprint,
Some(name),
)
} else if device_must_support_taproot
&& kind.map(|kind| is_compatible_with_tapminiscript(kind, version)) == Some(false)
{
hw::warning_hardware_wallet(
kind.map(|k| k.to_string()).unwrap_or_default(),
version,
fingerprint,
Some(name),
"Device firmware version does not support taproot miniscript",
)
} else {
hw::supported_hardware_wallet(
kind.map(|k| k.to_string()).unwrap_or_default(),
None::<String>,
version,
fingerprint,
Some(name),
)

View File

@ -10,7 +10,7 @@ use liana::{
};
pub struct Signer {
curve: secp256k1::Secp256k1<secp256k1::SignOnly>,
curve: secp256k1::Secp256k1<secp256k1::All>,
key: HotSigner,
pub fingerprint: Fingerprint,
}
@ -23,7 +23,7 @@ impl std::fmt::Debug for Signer {
impl Signer {
pub fn new(key: HotSigner) -> Self {
let curve = secp256k1::Secp256k1::signing_only();
let curve = secp256k1::Secp256k1::new();
let fingerprint = key.fingerprint(&curve);
Self {
key,

View File

@ -1,5 +1,6 @@
use iced::Color;
pub const BLACK: Color = iced::Color::BLACK;
pub const TRANSPARENT: Color = iced::Color::TRANSPARENT;
pub const LIGHT_BLACK: Color = Color::from_rgb(
0x14 as f32 / 255.0,
0x14 as f32 / 255.0,

View File

@ -51,11 +51,12 @@ pub fn supported_hardware_wallet<'a, T: 'a, K: Display, V: Display, F: Display>(
.padding(10)
}
pub fn unregistered_hardware_wallet<'a, T: 'a, K: Display, V: Display, F: Display>(
pub fn warning_hardware_wallet<'a, T: 'a, K: Display, V: Display, F: Display>(
kind: K,
version: Option<V>,
fingerprint: F,
alias: Option<impl Into<Cow<'a, str>>>,
warning: &'static str,
) -> Container<'a, T> {
container(
row(vec![
@ -75,7 +76,7 @@ pub fn unregistered_hardware_wallet<'a, T: 'a, K: Display, V: Display, F: Displa
.into(),
column(vec![tooltip::Tooltip::new(
icon::warning_icon(),
"The wallet descriptor is not registered on the device.\n You can register it in the settings.",
warning,
tooltip::Position::Bottom,
)
.style(theme::Container::Card(theme::Card::Simple))

View File

@ -441,6 +441,7 @@ pub enum PickList {
#[default]
Simple,
Invalid,
Secondary,
}
impl pick_list::StyleSheet for Theme {
type Style = PickList;
@ -465,6 +466,15 @@ impl pick_list::StyleSheet for Theme {
border_radius: 25.0,
text_color: color::RED,
},
PickList::Secondary => pick_list::Appearance {
placeholder_color: color::GREY_3,
handle_color: color::GREY_3,
background: color::TRANSPARENT.into(),
border_width: 1.0,
border_color: color::GREY_3,
border_radius: 25.0,
text_color: color::GREY_2,
},
}
}