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:
commit
4f78d3b3d0
92
gui/Cargo.lock
generated
92
gui/Cargo.lock
generated
@ -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",
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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())
|
||||
|
||||
@ -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,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@ -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)]
|
||||
|
||||
@ -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,
|
||||
),
|
||||
);
|
||||
|
||||
|
||||
@ -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),
|
||||
)
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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))
|
||||
|
||||
@ -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,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user