[descriptors] Transform a descriptor into its "public" version
This commit is contained in:
parent
0e432f3b26
commit
c1b01e4d8c
@ -101,7 +101,12 @@ async fn main() {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
cli::handle_matches(&Arc::clone(&wallet), matches.unwrap()).await;
|
if let Some(s) = cli::handle_matches(&Arc::clone(&wallet), matches.unwrap())
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
{
|
||||||
|
println!("{}", s);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Err(ReadlineError::Interrupted) => continue,
|
Err(ReadlineError::Interrupted) => continue,
|
||||||
Err(ReadlineError::Eof) => break,
|
Err(ReadlineError::Eof) => break,
|
||||||
@ -114,6 +119,8 @@ async fn main() {
|
|||||||
|
|
||||||
// rl.save_history("history.txt").unwrap();
|
// rl.save_history("history.txt").unwrap();
|
||||||
} else {
|
} else {
|
||||||
cli::handle_matches(&wallet, matches).await;
|
if let Some(s) = cli::handle_matches(&wallet, matches).await.unwrap() {
|
||||||
|
println!("{}", s);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
18
src/cli.rs
18
src/cli.rs
@ -119,6 +119,10 @@ pub fn make_cli_subcommands<'a, 'b>() -> App<'a, 'b> {
|
|||||||
SubCommand::with_name("policies")
|
SubCommand::with_name("policies")
|
||||||
.about("Returns the available spending policies for the descriptor")
|
.about("Returns the available spending policies for the descriptor")
|
||||||
)
|
)
|
||||||
|
.subcommand(
|
||||||
|
SubCommand::with_name("public_descriptor")
|
||||||
|
.about("Returns the public version of the wallet's descriptor(s)")
|
||||||
|
)
|
||||||
.subcommand(
|
.subcommand(
|
||||||
SubCommand::with_name("sign")
|
SubCommand::with_name("sign")
|
||||||
.about("Signs and tries to finalize a PSBT")
|
.about("Signs and tries to finalize a PSBT")
|
||||||
@ -271,6 +275,20 @@ where
|
|||||||
serde_json::to_string(&wallet.policies(ScriptType::External)?).unwrap(),
|
serde_json::to_string(&wallet.policies(ScriptType::External)?).unwrap(),
|
||||||
serde_json::to_string(&wallet.policies(ScriptType::Internal)?).unwrap(),
|
serde_json::to_string(&wallet.policies(ScriptType::Internal)?).unwrap(),
|
||||||
)))
|
)))
|
||||||
|
} else if let Some(_sub_matches) = matches.subcommand_matches("public_descriptor") {
|
||||||
|
let external = match wallet.public_descriptor(ScriptType::External)? {
|
||||||
|
Some(desc) => format!("{}", desc),
|
||||||
|
None => "<NONE>".into(),
|
||||||
|
};
|
||||||
|
let internal = match wallet.public_descriptor(ScriptType::Internal)? {
|
||||||
|
Some(desc) => format!("{}", desc),
|
||||||
|
None => "<NONE>".into(),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Some(format!(
|
||||||
|
"External: {}\nInternal:{}",
|
||||||
|
external, internal
|
||||||
|
)))
|
||||||
} else if let Some(sub_matches) = matches.subcommand_matches("sign") {
|
} else if let Some(sub_matches) = matches.subcommand_matches("sign") {
|
||||||
let psbt = base64::decode(sub_matches.value_of("psbt").unwrap()).unwrap();
|
let psbt = base64::decode(sub_matches.value_of("psbt").unwrap()).unwrap();
|
||||||
let psbt: PartiallySignedTransaction = deserialize(&psbt).unwrap();
|
let psbt: PartiallySignedTransaction = deserialize(&psbt).unwrap();
|
||||||
|
@ -199,6 +199,15 @@ impl FromStr for DescriptorExtendedKey {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if secret.is_none()
|
||||||
|
&& path.into_iter().any(|child| match child {
|
||||||
|
ChildNumber::Hardened { .. } => true,
|
||||||
|
_ => false,
|
||||||
|
})
|
||||||
|
{
|
||||||
|
return Err(super::Error::HardenedDerivationOnXpub);
|
||||||
|
}
|
||||||
|
|
||||||
Ok(DescriptorExtendedKey {
|
Ok(DescriptorExtendedKey {
|
||||||
master_fingerprint,
|
master_fingerprint,
|
||||||
master_derivation,
|
master_derivation,
|
||||||
|
181
src/descriptor/keys.rs
Normal file
181
src/descriptor/keys.rs
Normal file
@ -0,0 +1,181 @@
|
|||||||
|
use bitcoin::secp256k1::{All, Secp256k1};
|
||||||
|
use bitcoin::{PrivateKey, PublicKey};
|
||||||
|
|
||||||
|
use bitcoin::util::bip32::{
|
||||||
|
ChildNumber, DerivationPath, ExtendedPrivKey, ExtendedPubKey, Fingerprint,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::error::Error;
|
||||||
|
use super::extended_key::DerivationIndex;
|
||||||
|
use super::DescriptorExtendedKey;
|
||||||
|
|
||||||
|
pub(super) trait Key: std::fmt::Debug + std::fmt::Display {
|
||||||
|
fn fingerprint(&self, secp: &Secp256k1<All>) -> Option<Fingerprint>;
|
||||||
|
fn as_public_key(&self, secp: &Secp256k1<All>, index: Option<u32>) -> Result<PublicKey, Error>;
|
||||||
|
fn as_secret_key(&self) -> Option<PrivateKey>;
|
||||||
|
fn xprv(&self) -> Option<ExtendedPrivKey>;
|
||||||
|
fn full_path(&self, index: u32) -> Option<DerivationPath>;
|
||||||
|
fn is_fixed(&self) -> bool;
|
||||||
|
|
||||||
|
fn has_secret(&self) -> bool {
|
||||||
|
self.xprv().is_some() || self.as_secret_key().is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn public(&self, secp: &Secp256k1<All>) -> Result<Box<dyn Key>, Error> {
|
||||||
|
Ok(Box::new(self.as_public_key(secp, None)?))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Key for PublicKey {
|
||||||
|
fn fingerprint(&self, _secp: &Secp256k1<All>) -> Option<Fingerprint> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_public_key(
|
||||||
|
&self,
|
||||||
|
_secp: &Secp256k1<All>,
|
||||||
|
_index: Option<u32>,
|
||||||
|
) -> Result<PublicKey, Error> {
|
||||||
|
Ok(PublicKey::clone(self))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_secret_key(&self) -> Option<PrivateKey> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn xprv(&self) -> Option<ExtendedPrivKey> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn full_path(&self, _index: u32) -> Option<DerivationPath> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_fixed(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Key for PrivateKey {
|
||||||
|
fn fingerprint(&self, _secp: &Secp256k1<All>) -> Option<Fingerprint> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_public_key(
|
||||||
|
&self,
|
||||||
|
secp: &Secp256k1<All>,
|
||||||
|
_index: Option<u32>,
|
||||||
|
) -> Result<PublicKey, Error> {
|
||||||
|
Ok(self.public_key(secp))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_secret_key(&self) -> Option<PrivateKey> {
|
||||||
|
Some(PrivateKey::clone(self))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn xprv(&self) -> Option<ExtendedPrivKey> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn full_path(&self, _index: u32) -> Option<DerivationPath> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_fixed(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Key for DescriptorExtendedKey {
|
||||||
|
fn fingerprint(&self, secp: &Secp256k1<All>) -> Option<Fingerprint> {
|
||||||
|
if let Some(fing) = self.master_fingerprint {
|
||||||
|
Some(fing.clone())
|
||||||
|
} else {
|
||||||
|
Some(self.root_xpub(secp).fingerprint())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_public_key(&self, secp: &Secp256k1<All>, index: Option<u32>) -> Result<PublicKey, Error> {
|
||||||
|
Ok(self.derive_xpub(secp, index.unwrap_or(0))?.public_key)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn public(&self, secp: &Secp256k1<All>) -> Result<Box<dyn Key>, Error> {
|
||||||
|
if self.final_index == DerivationIndex::Hardened {
|
||||||
|
return Err(Error::HardenedDerivationOnXpub);
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.xprv().is_none() {
|
||||||
|
return Ok(Box::new(self.clone()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// copy the part of the path that can be derived on the xpub
|
||||||
|
let path = self
|
||||||
|
.path
|
||||||
|
.into_iter()
|
||||||
|
.rev()
|
||||||
|
.take_while(|child| match child {
|
||||||
|
ChildNumber::Normal { .. } => true,
|
||||||
|
_ => false,
|
||||||
|
})
|
||||||
|
.cloned()
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
// take the prefix that has to be derived on the xprv
|
||||||
|
let master_derivation_add = self
|
||||||
|
.path
|
||||||
|
.into_iter()
|
||||||
|
.take(self.path.as_ref().len() - path.len())
|
||||||
|
.cloned()
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let has_derived = !master_derivation_add.is_empty();
|
||||||
|
|
||||||
|
let derived_xprv = self
|
||||||
|
.secret
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.derive_priv(secp, &master_derivation_add)?;
|
||||||
|
let pubkey = ExtendedPubKey::from_private(secp, &derived_xprv);
|
||||||
|
|
||||||
|
let master_derivation = self
|
||||||
|
.master_derivation
|
||||||
|
.as_ref()
|
||||||
|
.map_or(vec![], |path| path.as_ref().to_vec())
|
||||||
|
.into_iter()
|
||||||
|
.chain(master_derivation_add.into_iter())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let master_derivation = match &master_derivation[..] {
|
||||||
|
&[] => None,
|
||||||
|
child_vec => Some(child_vec.into()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let master_fingerprint = match self.master_fingerprint {
|
||||||
|
Some(desc) => Some(desc.clone()),
|
||||||
|
None if has_derived => Some(self.fingerprint(secp).unwrap()),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Box::new(DescriptorExtendedKey {
|
||||||
|
master_fingerprint,
|
||||||
|
master_derivation,
|
||||||
|
pubkey,
|
||||||
|
secret: None,
|
||||||
|
path: path.into(),
|
||||||
|
final_index: self.final_index,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_secret_key(&self) -> Option<PrivateKey> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn xprv(&self) -> Option<ExtendedPrivKey> {
|
||||||
|
self.secret
|
||||||
|
}
|
||||||
|
|
||||||
|
fn full_path(&self, index: u32) -> Option<DerivationPath> {
|
||||||
|
Some(self.full_path(index))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_fixed(&self) -> bool {
|
||||||
|
self.final_index == DerivationIndex::Fixed
|
||||||
|
}
|
||||||
|
}
|
@ -19,13 +19,16 @@ use crate::psbt::utils::PSBTUtils;
|
|||||||
pub mod checksum;
|
pub mod checksum;
|
||||||
pub mod error;
|
pub mod error;
|
||||||
pub mod extended_key;
|
pub mod extended_key;
|
||||||
|
mod keys;
|
||||||
pub mod policy;
|
pub mod policy;
|
||||||
|
|
||||||
pub use self::checksum::get_checksum;
|
pub use self::checksum::get_checksum;
|
||||||
pub use self::error::Error;
|
use self::error::Error;
|
||||||
pub use self::extended_key::{DerivationIndex, DescriptorExtendedKey};
|
pub use self::extended_key::{DerivationIndex, DescriptorExtendedKey};
|
||||||
pub use self::policy::Policy;
|
pub use self::policy::Policy;
|
||||||
|
|
||||||
|
use self::keys::Key;
|
||||||
|
|
||||||
trait MiniscriptExtractPolicy {
|
trait MiniscriptExtractPolicy {
|
||||||
fn extract_policy(
|
fn extract_policy(
|
||||||
&self,
|
&self,
|
||||||
@ -105,109 +108,6 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
trait Key: std::fmt::Debug {
|
|
||||||
fn fingerprint(&self, secp: &Secp256k1<All>) -> Option<Fingerprint>;
|
|
||||||
fn as_public_key(&self, secp: &Secp256k1<All>, index: Option<u32>) -> Result<PublicKey, Error>;
|
|
||||||
fn as_secret_key(&self) -> Option<PrivateKey>;
|
|
||||||
fn xprv(&self) -> Option<ExtendedPrivKey>;
|
|
||||||
fn full_path(&self, index: u32) -> Option<DerivationPath>;
|
|
||||||
fn is_fixed(&self) -> bool;
|
|
||||||
|
|
||||||
fn has_secret(&self) -> bool {
|
|
||||||
self.xprv().is_some() || self.as_secret_key().is_some()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Key for PublicKey {
|
|
||||||
fn fingerprint(&self, _secp: &Secp256k1<All>) -> Option<Fingerprint> {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn as_public_key(
|
|
||||||
&self,
|
|
||||||
_secp: &Secp256k1<All>,
|
|
||||||
_index: Option<u32>,
|
|
||||||
) -> Result<PublicKey, Error> {
|
|
||||||
Ok(PublicKey::clone(self))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn as_secret_key(&self) -> Option<PrivateKey> {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn xprv(&self) -> Option<ExtendedPrivKey> {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn full_path(&self, _index: u32) -> Option<DerivationPath> {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_fixed(&self) -> bool {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Key for PrivateKey {
|
|
||||||
fn fingerprint(&self, _secp: &Secp256k1<All>) -> Option<Fingerprint> {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn as_public_key(
|
|
||||||
&self,
|
|
||||||
secp: &Secp256k1<All>,
|
|
||||||
_index: Option<u32>,
|
|
||||||
) -> Result<PublicKey, Error> {
|
|
||||||
Ok(self.public_key(secp))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn as_secret_key(&self) -> Option<PrivateKey> {
|
|
||||||
Some(PrivateKey::clone(self))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn xprv(&self) -> Option<ExtendedPrivKey> {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn full_path(&self, _index: u32) -> Option<DerivationPath> {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_fixed(&self) -> bool {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Key for DescriptorExtendedKey {
|
|
||||||
fn fingerprint(&self, secp: &Secp256k1<All>) -> Option<Fingerprint> {
|
|
||||||
if let Some(fing) = self.master_fingerprint {
|
|
||||||
Some(fing.clone())
|
|
||||||
} else {
|
|
||||||
Some(self.root_xpub(secp).fingerprint())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn as_public_key(&self, secp: &Secp256k1<All>, index: Option<u32>) -> Result<PublicKey, Error> {
|
|
||||||
Ok(self.derive_xpub(secp, index.unwrap_or(0))?.public_key)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn as_secret_key(&self) -> Option<PrivateKey> {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn xprv(&self) -> Option<ExtendedPrivKey> {
|
|
||||||
self.secret
|
|
||||||
}
|
|
||||||
|
|
||||||
fn full_path(&self, index: u32) -> Option<DerivationPath> {
|
|
||||||
Some(self.full_path(index))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_fixed(&self) -> bool {
|
|
||||||
self.final_index == DerivationIndex::Fixed
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[serde(try_from = "&str", into = "String")]
|
#[serde(try_from = "&str", into = "String")]
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
pub struct ExtendedDescriptor {
|
pub struct ExtendedDescriptor {
|
||||||
@ -221,6 +121,12 @@ pub struct ExtendedDescriptor {
|
|||||||
ctx: Secp256k1<All>,
|
ctx: Secp256k1<All>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for ExtendedDescriptor {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "{}", self.internal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl std::clone::Clone for ExtendedDescriptor {
|
impl std::clone::Clone for ExtendedDescriptor {
|
||||||
fn clone(&self) -> Self {
|
fn clone(&self) -> Self {
|
||||||
Self {
|
Self {
|
||||||
@ -421,6 +327,35 @@ impl ExtendedDescriptor {
|
|||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn as_public_version(&self) -> Result<ExtendedDescriptor, Error> {
|
||||||
|
let keys: RefCell<BTreeMap<String, Box<dyn Key>>> = RefCell::new(BTreeMap::new());
|
||||||
|
|
||||||
|
let translatefpk = |string: &String| -> Result<_, Error> {
|
||||||
|
let public = self.keys.get(string).unwrap().public(&self.ctx)?;
|
||||||
|
|
||||||
|
let result = format!("{}", public);
|
||||||
|
keys.borrow_mut().insert(string.clone(), public);
|
||||||
|
|
||||||
|
Ok(result)
|
||||||
|
};
|
||||||
|
let translatefpkh = |string: &String| -> Result<_, Error> {
|
||||||
|
let public = self.keys.get(string).unwrap().public(&self.ctx)?;
|
||||||
|
|
||||||
|
let result = format!("{}", public);
|
||||||
|
keys.borrow_mut().insert(string.clone(), public);
|
||||||
|
|
||||||
|
Ok(result)
|
||||||
|
};
|
||||||
|
|
||||||
|
let internal = self.internal.translate_pk(translatefpk, translatefpkh)?;
|
||||||
|
|
||||||
|
Ok(ExtendedDescriptor {
|
||||||
|
internal,
|
||||||
|
keys: keys.into_inner(),
|
||||||
|
ctx: self.ctx.clone(),
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ExtractPolicy for ExtendedDescriptor {
|
impl ExtractPolicy for ExtendedDescriptor {
|
||||||
|
@ -496,6 +496,17 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn public_descriptor(
|
||||||
|
&self,
|
||||||
|
script_type: ScriptType,
|
||||||
|
) -> Result<Option<ExtendedDescriptor>, Error> {
|
||||||
|
match (script_type, self.change_descriptor.as_ref()) {
|
||||||
|
(ScriptType::External, _) => Ok(Some(self.descriptor.as_public_version()?)),
|
||||||
|
(ScriptType::Internal, None) => Ok(None),
|
||||||
|
(ScriptType::Internal, Some(desc)) => Ok(Some(desc.as_public_version()?)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Internals
|
// Internals
|
||||||
|
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
Loading…
x
Reference in New Issue
Block a user