From 81a4a83d7fd3a50dca75c1bddd0431f66d3ac863 Mon Sep 17 00:00:00 2001 From: Leonardo Comandini Date: Wed, 29 Nov 2023 14:51:12 +0100 Subject: [PATCH] CT descriptor: use hex for single private view keys Also add a test for the elip150 test vector that changed. --- src/confidential/mod.rs | 38 ++++++++++++++++++++++++++++++++++++-- src/descriptor/key.rs | 29 +++++++++++++++++++++++------ src/descriptor/mod.rs | 1 + 3 files changed, 60 insertions(+), 8 deletions(-) diff --git a/src/confidential/mod.rs b/src/confidential/mod.rs index d1f2265e..1e027e2a 100644 --- a/src/confidential/mod.rs +++ b/src/confidential/mod.rs @@ -50,7 +50,17 @@ impl fmt::Display for Key { match self { Key::Slip77(data) => write!(f, "slip77({})", data), Key::Bare(pk) => fmt::Display::fmt(pk, f), - Key::View(sk) => fmt::Display::fmt(sk, f), + Key::View(sk) => { + if let DescriptorSecretKey::Single(sk) = sk { + crate::descriptor::maybe_fmt_master_id(f, &sk.origin)?; + for byte in &sk.key.inner.secret_bytes() { + write!(f, "{:02x}", byte)?; + } + Ok(()) + } else { + fmt::Display::fmt(sk, f) + } + } } } } @@ -211,7 +221,7 @@ impl_from_str!( "slip77() must have exactly one argument".to_owned() )), _ => expression::terminal(keyexpr, DescriptorPublicKey::from_str).map(Key::Bare) - .or_else(|_| expression::terminal(keyexpr, DescriptorSecretKey::from_str).map(Key::View))?, + .or_else(|_| expression::terminal(keyexpr, |s: &str| DescriptorSecretKey::from_str_inner(s, true)).map(Key::View))?, }, descriptor: crate::Descriptor::from_tree(&top.args[1])?, }) @@ -420,6 +430,10 @@ mod tests { "ct(pk(02dce16018bbbb8e36de7b394df5b5166e9adb7498be7d881a85a09aeecf76b623),elwpkh(03774eec7a3d550d18e9f89414152025b3b0ad6a342b19481f702d843cff06dfc4))#nvax6rau", "unexpected «pk»", ), + ( + "ct(L3jXxwef3fpB7hcrFozcWgHeJCPSAFiZ1Ji2YJMPxceaGvy3PC1q,elwpkh(03774eec7a3d550d18e9f89414152025b3b0ad6a342b19481f702d843cff06dfc4))#gcy6hcfz", + "unexpected «Error while parsing xkey.»", + ), ]; /* @@ -470,6 +484,26 @@ mod tests { test.check(&secp); } + #[test] + fn view_single_key_descriptor() { + let secp = secp256k1_zkp::Secp256k1::new(); + let view_key = "c25deb86fa11e49d651d7eae27c220ef930fbd86ea023eebfa73e54875647963"; + let ct_key = "0286fc9a38e765d955e9b0bcc18fa9ae81b0c893e2dd1ef5542a9c73780a086b90"; + let pk = "021a8fb6bd5a653b021b98a2a785725b8ddacfe3687bc043aa7f4d25d3a48d40b5"; + let addr_conf = "el1qq265u4g3k3m3qpyxjwpdrtnm293wuxgvs9xzmzcs2ck0mv5rx23w4d7xfsednsmmxrszfe7s9rs0c6cvf3dfyqwa4jj40uffq"; + let addr_unconf = "ert1qklrycvkecdanpcpyulgz3c8udvxyck5jkzxddw"; + + for desc_str in [ + format!("ct({view_key},elwpkh({pk}))#c2kx9zll"), + format!("ct({ct_key},elwpkh({pk}))#m5mvyh29"), + ] { + let desc = Descriptor::::from_str(&desc_str).unwrap(); + assert_eq!(desc.to_string(), desc_str); + assert_eq!(addr_conf, &desc.address(&secp, &elements::AddressParams::ELEMENTS).unwrap().to_string()); + assert_eq!(addr_unconf, &desc.unconfidential_address(&elements::AddressParams::ELEMENTS).unwrap().to_string()); + } + } + #[test] fn descriptor_wildcard() { let secp = secp256k1_zkp::Secp256k1::new(); diff --git a/src/descriptor/key.rs b/src/descriptor/key.rs index ab55a61a..6f0b94ec 100644 --- a/src/descriptor/key.rs +++ b/src/descriptor/key.rs @@ -7,6 +7,7 @@ use std::{error, fmt}; use bitcoin::hash_types::XpubIdentifier; use bitcoin::{self, bip32}; use elements::hashes::{hash160, ripemd160, sha256, Hash, HashEngine}; +use elements::hex::FromHex; use elements::secp256k1_zkp::{Secp256k1, Signing, Verification}; #[cfg(feature = "serde")] @@ -382,7 +383,7 @@ impl DescriptorSecretKey { } /// Writes the fingerprint of the origin, if there is one. -fn maybe_fmt_master_id( +pub(crate) fn maybe_fmt_master_id( f: &mut fmt::Formatter<'_>, origin: &Option<(bip32::Fingerprint, bip32::DerivationPath)>, ) -> fmt::Result { @@ -691,13 +692,21 @@ impl DescriptorPublicKey { } } -impl FromStr for DescriptorSecretKey { - type Err = DescriptorKeyParseError; - - fn from_str(s: &str) -> Result { +impl DescriptorSecretKey { + pub(crate) fn from_str_inner(s: &str, single_hex: bool) -> Result { let (key_part, origin) = parse_key_origin(s)?; - if key_part.len() <= 52 { + if single_hex && key_part.len() == 64 { + let bytes = Vec::::from_hex(key_part) + .map_err(|_| DescriptorKeyParseError("Error while parsing a HEX private key"))?; + let network = bitcoin::network::constants::Network::Bitcoin; // Use any network + let sk = bitcoin::PrivateKey::from_slice(&bytes, network) + .map_err(|_| DescriptorKeyParseError("Error while parsing a HEX private key"))?; + Ok(DescriptorSecretKey::Single(SinglePriv { + key: sk, + origin: None, + })) + } else if !single_hex && key_part.len() <= 52 { let sk = bitcoin::PrivateKey::from_str(key_part) .map_err(|_| DescriptorKeyParseError("Error while parsing a WIF private key"))?; Ok(DescriptorSecretKey::Single(SinglePriv { @@ -726,6 +735,14 @@ impl FromStr for DescriptorSecretKey { } } +impl FromStr for DescriptorSecretKey { + type Err = DescriptorKeyParseError; + + fn from_str(s: &str) -> Result { + Self::from_str_inner(s, false) + } +} + // Parse the origin information part of a descriptor key. fn parse_key_origin(s: &str) -> Result<(&str, Option), DescriptorKeyParseError> { for ch in s.as_bytes() { diff --git a/src/descriptor/mod.rs b/src/descriptor/mod.rs index 9a164562..48397b15 100644 --- a/src/descriptor/mod.rs +++ b/src/descriptor/mod.rs @@ -55,6 +55,7 @@ pub use self::key::{ DescriptorMultiXKey, DescriptorPublicKey, DescriptorSecretKey, DescriptorXKey, InnerXKey, SinglePriv, SinglePub, SinglePubKey, Wildcard, }; +pub(crate) use self::key::maybe_fmt_master_id; pub use self::tr::{TapTree, Tr, TapLeafScript}; /// Alias type for a map of public key to secret key ///