]> Untitled Git - bdk/commitdiff
Use `miniscript::DescriptorPublicKey`
authorAlekos Filini <alekos.filini@gmail.com>
Wed, 12 Aug 2020 10:51:50 +0000 (12:51 +0200)
committerAlekos Filini <alekos.filini@gmail.com>
Sun, 30 Aug 2020 18:36:19 +0000 (20:36 +0200)
This allows us to remove all our custom "ExtendedDescriptor" implementation since that is
now built directly in miniscript.

19 files changed:
Cargo.toml
examples/parse_descriptor.rs
src/blockchain/compact_filters/mod.rs
src/blockchain/compact_filters/store.rs
src/descriptor/extended_key.rs [deleted file]
src/descriptor/keys.rs [deleted file]
src/descriptor/mod.rs
src/descriptor/policy.rs
src/error.rs
src/lib.rs
src/psbt/mod.rs
src/psbt/utils.rs [deleted file]
src/signer.rs [deleted file]
src/wallet/export.rs
src/wallet/mod.rs
src/wallet/signer.rs [new file with mode: 0644]
src/wallet/utils.rs
testutils-macros/src/lib.rs
testutils/src/lib.rs

index 8b5bea3e1625467e6b18ff0bb9e62cc27d207f8a..f9d4b30197f617b820b70400c170bb2507963223 100644 (file)
@@ -2,7 +2,7 @@
 name = "magical-bitcoin-wallet"
 version = "0.1.0"
 edition = "2018"
-authors = ["Riccardo Casatta <riccardo@casatta.it>", "Alekos Filini <alekos.filini@gmail.com>"]
+authors = ["Alekos Filini <alekos.filini@gmail.com>", "Riccardo Casatta <riccardo@casatta.it>"]
 
 [dependencies]
 magical-macros = { path = "./macros" }
@@ -25,6 +25,10 @@ rocksdb = { version = "0.14", optional = true }
 socks = { version = "0.3", optional = true }
 lazy_static = { version = "1.4", optional = true }
 
+[patch.crates-io]
+bitcoin = { git = "https://github.com/rust-bitcoin/rust-bitcoin/", rev = "478e091" }
+miniscript = { git = "https://github.com/MagicalBitcoin/rust-miniscript", branch = "descriptor-public-key" }
+
 # Platform-specific dependencies
 [target.'cfg(not(target_arch = "wasm32"))'.dependencies]
 tokio = { version = "0.2", features = ["rt-core"] }
index e21ddb4c6d3423b89b5aa8883fd6889fbc6e32ee..4885973fa08531cc3e8a121884b6c4587a2815f0 100644 (file)
@@ -1,8 +1,9 @@
 extern crate magical_bitcoin_wallet;
 extern crate serde_json;
 
-use std::str::FromStr;
+use std::sync::Arc;
 
+use magical_bitcoin_wallet::bitcoin::util::bip32::ChildNumber;
 use magical_bitcoin_wallet::bitcoin::*;
 use magical_bitcoin_wallet::descriptor::*;
 
@@ -14,13 +15,14 @@ fn main() {
                     and_v(vc:pk_h(cVt4o7BGAig1UXywgGSmARhxMdzP5qvQsxKkSsc1XEkw3tDTQFpy),older(1000))\
                    ))";
 
-    let extended_desc = ExtendedDescriptor::from_str(desc).unwrap();
+    let (extended_desc, key_map) = ExtendedDescriptor::parse_secret(desc).unwrap();
     println!("{:?}", extended_desc);
 
-    let policy = extended_desc.extract_policy().unwrap();
+    let signers = Arc::new(key_map.into());
+    let policy = extended_desc.extract_policy(signers).unwrap();
     println!("policy: {}", serde_json::to_string(&policy).unwrap());
 
-    let derived_desc = extended_desc.derive(42).unwrap();
+    let derived_desc = extended_desc.derive(&[ChildNumber::from_normal_idx(42).unwrap()]);
     println!("{:?}", derived_desc);
 
     let addr = derived_desc.address(Network::Testnet).unwrap();
index 4e33af466a81d21d2782ce5c8809147ddb42f587..8e8b01351975f31ece55d256fec9e485d1c8a4e8 100644 (file)
@@ -7,7 +7,7 @@ use std::sync::{Arc, Mutex};
 use log::{debug, error, info, trace};
 
 use bitcoin::network::message_blockdata::Inventory;
-use bitcoin::{BitcoinHash, OutPoint, Transaction, Txid};
+use bitcoin::{OutPoint, Transaction, Txid};
 
 use rocksdb::{Options, SliceTransform, DB};
 
@@ -274,7 +274,7 @@ impl OnlineBlockchain for CompactFiltersBlockchain {
 
                         let block_height = headers.get_height_for(block_hash)?.unwrap_or(0);
                         let saved_correct_block = match headers.get_full_block(block_height)? {
-                            Some(block) if &block.bitcoin_hash() == block_hash => true,
+                            Some(block) if &block.block_hash() == block_hash => true,
                             _ => false,
                         };
 
index bfd570cd9691b626e2693bbc6af740d15569c417..8ea657a819e7030816652e71568c09e6eef4adaf 100644 (file)
@@ -16,7 +16,6 @@ use bitcoin::hash_types::FilterHash;
 use bitcoin::hashes::hex::FromHex;
 use bitcoin::hashes::{sha256d, Hash};
 use bitcoin::util::bip158::BlockFilter;
-use bitcoin::util::hash::BitcoinHash;
 use bitcoin::util::uint::Uint256;
 use bitcoin::Block;
 use bitcoin::BlockHash;
@@ -257,7 +256,7 @@ impl ChainStore<Full> {
             );
             batch.put_cf(
                 cf_handle,
-                StoreEntry::BlockHeaderIndex(Some(genesis.bitcoin_hash())).get_key(),
+                StoreEntry::BlockHeaderIndex(Some(genesis.block_hash())).get_key(),
                 &0usize.to_be_bytes(),
             );
             store.write(batch)?;
@@ -290,7 +289,7 @@ impl ChainStore<Full> {
                     .get_pinned_cf(cf_handle, StoreEntry::BlockHeader(Some(index)).get_key())?
                     .unwrap(),
             )?;
-            answer.push((header.bitcoin_hash(), index));
+            answer.push((header.block_hash(), index));
 
             if let Some(new_index) = index.checked_sub(step) {
                 index = new_index;
@@ -322,7 +321,7 @@ impl ChainStore<Full> {
         let mut batch = WriteBatch::default();
         batch.put_cf(
             new_cf_handle,
-            StoreEntry::BlockHeaderIndex(Some(header.bitcoin_hash())).get_key(),
+            StoreEntry::BlockHeaderIndex(Some(header.block_hash())).get_key(),
             &from.to_be_bytes(),
         );
         batch.put_cf(
@@ -406,7 +405,7 @@ impl ChainStore<Full> {
 
             batch.delete_cf(
                 cf_handle,
-                StoreEntry::BlockHeaderIndex(Some(header.bitcoin_hash())).get_key(),
+                StoreEntry::BlockHeaderIndex(Some(header.block_hash())).get_key(),
             );
         }
 
@@ -461,7 +460,7 @@ impl ChainStore<Full> {
             .map(|data| {
                 let (header, _): (BlockHeader, Uint256) =
                     deserialize(&data).map_err(|_| CompactFiltersError::DataCorruption)?;
-                Ok::<_, CompactFiltersError>(header.bitcoin_hash())
+                Ok::<_, CompactFiltersError>(header.block_hash())
             })
             .transpose()?)
     }
@@ -574,7 +573,7 @@ impl<T: StoreType> ChainStore<T> {
             .map(|(_, v)| -> Result<_, CompactFiltersError> {
                 let (header, _): (BlockHeader, Uint256) = SerializeDb::deserialize(&v)?;
 
-                Ok(header.bitcoin_hash())
+                Ok(header.block_hash())
             })
             .transpose()?)
     }
@@ -593,7 +592,7 @@ impl<T: StoreType> ChainStore<T> {
             .get_pinned_cf(cf_handle, StoreEntry::BlockHeader(Some(from)).get_key())?
             .map(|result| {
                 let (header, work): (BlockHeader, Uint256) = SerializeDb::deserialize(&result)?;
-                Ok::<_, CompactFiltersError>((header.bitcoin_hash(), work))
+                Ok::<_, CompactFiltersError>((header.block_hash(), work))
             })
             .transpose()?
             .ok_or(CompactFiltersError::DataCorruption)?;
@@ -603,13 +602,13 @@ impl<T: StoreType> ChainStore<T> {
                 return Err(CompactFiltersError::InvalidHeaders);
             }
 
-            last_hash = header.bitcoin_hash();
+            last_hash = header.block_hash();
             accumulated_work = accumulated_work + header.work();
 
             let height = from + index + 1;
             batch.put_cf(
                 cf_handle,
-                StoreEntry::BlockHeaderIndex(Some(header.bitcoin_hash())).get_key(),
+                StoreEntry::BlockHeaderIndex(Some(header.block_hash())).get_key(),
                 &(height).to_be_bytes(),
             );
             batch.put_cf(
@@ -647,8 +646,8 @@ pub struct FilterHeader {
     filter_hash: FilterHash,
 }
 
-impl BitcoinHash<FilterHeaderHash> for FilterHeader {
-    fn bitcoin_hash(&self) -> FilterHeaderHash {
+impl FilterHeader {
+    fn header_hash(&self) -> FilterHeaderHash {
         let mut hash_data = self.filter_hash.into_inner().to_vec();
         hash_data.extend_from_slice(&self.prev_header_hash);
         sha256d::Hash::hash(&hash_data).into()
@@ -794,7 +793,7 @@ impl CFStore {
                     prev_header_hash: last_hash,
                     filter_hash,
                 };
-                last_hash = filter_header.bitcoin_hash();
+                last_hash = filter_header.header_hash();
 
                 filter_header
             })
diff --git a/src/descriptor/extended_key.rs b/src/descriptor/extended_key.rs
deleted file mode 100644 (file)
index 70cc208..0000000
+++ /dev/null
@@ -1,372 +0,0 @@
-use std::fmt::{self, Display};
-use std::str::FromStr;
-
-use bitcoin::hashes::hex::{FromHex, ToHex};
-use bitcoin::secp256k1;
-use bitcoin::util::base58;
-use bitcoin::util::bip32::{
-    ChildNumber, DerivationPath, ExtendedPrivKey, ExtendedPubKey, Fingerprint,
-};
-use bitcoin::PublicKey;
-
-#[allow(unused_imports)]
-use log::{debug, error, info, trace};
-
-#[derive(Copy, Clone, PartialEq, Eq, Debug)]
-pub enum DerivationIndex {
-    Fixed,
-    Normal,
-    Hardened,
-}
-
-impl DerivationIndex {
-    fn as_path(&self, index: u32) -> DerivationPath {
-        match self {
-            DerivationIndex::Fixed => vec![],
-            DerivationIndex::Normal => vec![ChildNumber::Normal { index }],
-            DerivationIndex::Hardened => vec![ChildNumber::Hardened { index }],
-        }
-        .into()
-    }
-}
-
-impl Display for DerivationIndex {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        let chars = match *self {
-            Self::Fixed => "",
-            Self::Normal => "/*",
-            Self::Hardened => "/*'",
-        };
-
-        write!(f, "{}", chars)
-    }
-}
-
-#[derive(Clone, Debug)]
-pub struct DescriptorExtendedKey {
-    pub master_fingerprint: Option<Fingerprint>,
-    pub master_derivation: Option<DerivationPath>,
-    pub pubkey: ExtendedPubKey,
-    pub secret: Option<ExtendedPrivKey>,
-    pub path: DerivationPath,
-    pub final_index: DerivationIndex,
-}
-
-impl DescriptorExtendedKey {
-    pub fn full_path(&self, index: u32) -> DerivationPath {
-        let mut final_path: Vec<ChildNumber> = Vec::new();
-        if let Some(path) = &self.master_derivation {
-            let path_as_vec: Vec<ChildNumber> = path.clone().into();
-            final_path.extend_from_slice(&path_as_vec);
-        }
-        let our_path: Vec<ChildNumber> = self.path_with_index(index).into();
-        final_path.extend_from_slice(&our_path);
-
-        final_path.into()
-    }
-
-    pub fn path_with_index(&self, index: u32) -> DerivationPath {
-        let mut final_path: Vec<ChildNumber> = Vec::new();
-        let our_path: Vec<ChildNumber> = self.path.clone().into();
-        final_path.extend_from_slice(&our_path);
-        let other_path: Vec<ChildNumber> = self.final_index.as_path(index).into();
-        final_path.extend_from_slice(&other_path);
-
-        final_path.into()
-    }
-
-    pub fn derive<C: secp256k1::Verification + secp256k1::Signing>(
-        &self,
-        ctx: &secp256k1::Secp256k1<C>,
-        index: u32,
-    ) -> Result<PublicKey, super::Error> {
-        Ok(self.derive_xpub(ctx, index)?.public_key)
-    }
-
-    pub fn derive_xpub<C: secp256k1::Verification + secp256k1::Signing>(
-        &self,
-        ctx: &secp256k1::Secp256k1<C>,
-        index: u32,
-    ) -> Result<ExtendedPubKey, super::Error> {
-        if let Some(xprv) = self.secret {
-            let derive_priv = xprv.derive_priv(ctx, &self.path_with_index(index))?;
-            Ok(ExtendedPubKey::from_private(ctx, &derive_priv))
-        } else {
-            Ok(self.pubkey.derive_pub(ctx, &self.path_with_index(index))?)
-        }
-    }
-
-    pub fn root_xpub<C: secp256k1::Verification + secp256k1::Signing>(
-        &self,
-        ctx: &secp256k1::Secp256k1<C>,
-    ) -> ExtendedPubKey {
-        if let Some(ref xprv) = self.secret {
-            ExtendedPubKey::from_private(ctx, xprv)
-        } else {
-            self.pubkey
-        }
-    }
-}
-
-impl Display for DescriptorExtendedKey {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        if let Some(ref fingerprint) = self.master_fingerprint {
-            write!(f, "[{}", fingerprint.to_hex())?;
-            if let Some(ref path) = self.master_derivation {
-                write!(f, "{}", &path.to_string()[1..])?;
-            }
-            write!(f, "]")?;
-        }
-
-        if let Some(xprv) = self.secret {
-            write!(f, "{}", xprv)?
-        } else {
-            write!(f, "{}", self.pubkey)?
-        }
-
-        write!(f, "{}{}", &self.path.to_string()[1..], self.final_index)
-    }
-}
-
-impl FromStr for DescriptorExtendedKey {
-    type Err = super::Error;
-
-    fn from_str(inp: &str) -> Result<DescriptorExtendedKey, Self::Err> {
-        let len = inp.len();
-
-        let (master_fingerprint, master_derivation, offset) = match inp.starts_with("[") {
-            false => (None, None, 0),
-            true => {
-                if inp.len() < 9 {
-                    return Err(super::Error::MalformedInput);
-                }
-
-                let master_fingerprint = &inp[1..9];
-                let close_bracket_index =
-                    &inp[9..].find("]").ok_or(super::Error::MalformedInput)?;
-                let path = if *close_bracket_index > 0 {
-                    Some(DerivationPath::from_str(&format!(
-                        "m{}",
-                        &inp[9..9 + *close_bracket_index]
-                    ))?)
-                } else {
-                    None
-                };
-
-                (
-                    Some(Fingerprint::from_hex(master_fingerprint)?),
-                    path,
-                    9 + *close_bracket_index + 1,
-                )
-            }
-        };
-
-        let (key_range, offset) = match &inp[offset..].find("/") {
-            Some(index) => (offset..offset + *index, offset + *index),
-            None => (offset..len, len),
-        };
-        let data = base58::from_check(&inp[key_range.clone()])?;
-        let secp = secp256k1::Secp256k1::new();
-        let (pubkey, secret) = match &data[0..4] {
-            [0x04u8, 0x88, 0xB2, 0x1E] | [0x04u8, 0x35, 0x87, 0xCF] => {
-                (ExtendedPubKey::from_str(&inp[key_range])?, None)
-            }
-            [0x04u8, 0x88, 0xAD, 0xE4] | [0x04u8, 0x35, 0x83, 0x94] => {
-                let private = ExtendedPrivKey::from_str(&inp[key_range])?;
-                (ExtendedPubKey::from_private(&secp, &private), Some(private))
-            }
-            data => return Err(super::Error::InvalidPrefix(data.into())),
-        };
-
-        let (path, final_index, _) = match &inp[offset..].starts_with("/") {
-            false => (DerivationPath::from(vec![]), DerivationIndex::Fixed, offset),
-            true => {
-                let (all, skip) = match &inp[len - 2..len] {
-                    "/*" => (DerivationIndex::Normal, 2),
-                    "*'" | "*h" => (DerivationIndex::Hardened, 3),
-                    _ => (DerivationIndex::Fixed, 0),
-                };
-
-                if all == DerivationIndex::Hardened && secret.is_none() {
-                    return Err(super::Error::HardenedDerivationOnXpub);
-                }
-
-                (
-                    DerivationPath::from_str(&format!("m{}", &inp[offset..len - skip]))?,
-                    all,
-                    len,
-                )
-            }
-        };
-
-        if secret.is_none()
-            && path.into_iter().any(|child| match child {
-                ChildNumber::Hardened { .. } => true,
-                _ => false,
-            })
-        {
-            return Err(super::Error::HardenedDerivationOnXpub);
-        }
-
-        Ok(DescriptorExtendedKey {
-            master_fingerprint,
-            master_derivation,
-            pubkey,
-            secret,
-            path,
-            final_index,
-        })
-    }
-}
-
-#[cfg(test)]
-mod test {
-    use std::str::FromStr;
-
-    use bitcoin::hashes::hex::FromHex;
-    use bitcoin::util::bip32::{ChildNumber, DerivationPath};
-
-    use crate::descriptor::*;
-
-    macro_rules! hex_fingerprint {
-        ($hex:expr) => {
-            Fingerprint::from_hex($hex).unwrap()
-        };
-    }
-
-    macro_rules! deriv_path {
-        ($str:expr) => {
-            DerivationPath::from_str($str).unwrap()
-        };
-
-        () => {
-            DerivationPath::from(vec![])
-        };
-    }
-
-    #[test]
-    fn test_derivation_index_fixed() {
-        let index = DerivationIndex::Fixed;
-        assert_eq!(index.as_path(1337), DerivationPath::from(vec![]));
-        assert_eq!(format!("{}", index), "");
-    }
-
-    #[test]
-    fn test_derivation_index_normal() {
-        let index = DerivationIndex::Normal;
-        assert_eq!(
-            index.as_path(1337),
-            DerivationPath::from(vec![ChildNumber::Normal { index: 1337 }])
-        );
-        assert_eq!(format!("{}", index), "/*");
-    }
-
-    #[test]
-    fn test_derivation_index_hardened() {
-        let index = DerivationIndex::Hardened;
-        assert_eq!(
-            index.as_path(1337),
-            DerivationPath::from(vec![ChildNumber::Hardened { index: 1337 }])
-        );
-        assert_eq!(format!("{}", index), "/*'");
-    }
-
-    #[test]
-    fn test_parse_xpub_no_path_fixed() {
-        let key = "xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL";
-        let ek = DescriptorExtendedKey::from_str(key).unwrap();
-        assert_eq!(ek.pubkey.fingerprint(), hex_fingerprint!("31a507b8"));
-        assert_eq!(ek.path, deriv_path!());
-        assert_eq!(ek.final_index, DerivationIndex::Fixed);
-    }
-
-    #[test]
-    fn test_parse_xpub_with_path_fixed() {
-        let key = "xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/1/2/3";
-        let ek = DescriptorExtendedKey::from_str(key).unwrap();
-        assert_eq!(ek.pubkey.fingerprint(), hex_fingerprint!("31a507b8"));
-        assert_eq!(ek.path, deriv_path!("m/1/2/3"));
-        assert_eq!(ek.final_index, DerivationIndex::Fixed);
-    }
-
-    #[test]
-    fn test_parse_xpub_with_path_normal() {
-        let key = "xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/1/2/3/*";
-        let ek = DescriptorExtendedKey::from_str(key).unwrap();
-        assert_eq!(ek.pubkey.fingerprint(), hex_fingerprint!("31a507b8"));
-        assert_eq!(ek.path, deriv_path!("m/1/2/3"));
-        assert_eq!(ek.final_index, DerivationIndex::Normal);
-    }
-
-    #[test]
-    #[should_panic(expected = "HardenedDerivationOnXpub")]
-    fn test_parse_xpub_with_path_hardened() {
-        let key = "xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/*'";
-        let ek = DescriptorExtendedKey::from_str(key).unwrap();
-        assert_eq!(ek.pubkey.fingerprint(), hex_fingerprint!("31a507b8"));
-        assert_eq!(ek.path, deriv_path!("m/1/2/3"));
-        assert_eq!(ek.final_index, DerivationIndex::Fixed);
-    }
-
-    #[test]
-    fn test_parse_tprv_with_path_hardened() {
-        let key = "tprv8ZgxMBicQKsPduL5QnGihpprdHyypMGi4DhimjtzYemu7se5YQNcZfAPLqXRuGHb5ZX2eTQj62oNqMnyxJ7B7wz54Uzswqw8fFqMVdcmVF7/1/2/3/*'";
-        let ek = DescriptorExtendedKey::from_str(key).unwrap();
-        assert!(ek.secret.is_some());
-        assert_eq!(ek.pubkey.fingerprint(), hex_fingerprint!("5ea4190e"));
-        assert_eq!(ek.path, deriv_path!("m/1/2/3"));
-        assert_eq!(ek.final_index, DerivationIndex::Hardened);
-    }
-
-    #[test]
-    fn test_parse_xpub_master_details() {
-        let key = "[d34db33f/44'/0'/0']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL";
-        let ek = DescriptorExtendedKey::from_str(key).unwrap();
-        assert_eq!(ek.master_fingerprint, Some(hex_fingerprint!("d34db33f")));
-        assert_eq!(ek.master_derivation, Some(deriv_path!("m/44'/0'/0'")));
-    }
-
-    #[test]
-    fn test_parse_xpub_master_details_empty_derivation() {
-        let key = "[d34db33f]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL";
-        let ek = DescriptorExtendedKey::from_str(key).unwrap();
-        assert_eq!(ek.master_fingerprint, Some(hex_fingerprint!("d34db33f")));
-        assert_eq!(ek.master_derivation, None);
-    }
-
-    #[test]
-    #[should_panic(expected = "MalformedInput")]
-    fn test_parse_xpub_short_input() {
-        let key = "[d34d";
-        DescriptorExtendedKey::from_str(key).unwrap();
-    }
-
-    #[test]
-    #[should_panic(expected = "MalformedInput")]
-    fn test_parse_xpub_missing_closing_bracket() {
-        let key = "[d34db33fxpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL";
-        DescriptorExtendedKey::from_str(key).unwrap();
-    }
-
-    #[test]
-    #[should_panic(expected = "InvalidChar")]
-    fn test_parse_xpub_invalid_fingerprint() {
-        let key = "[d34db33z]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL";
-        DescriptorExtendedKey::from_str(key).unwrap();
-    }
-
-    #[test]
-    fn test_xpub_normal_full_path() {
-        let key = "xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/1/2/*";
-        let ek = DescriptorExtendedKey::from_str(key).unwrap();
-        assert_eq!(ek.full_path(42), deriv_path!("m/1/2/42"));
-    }
-
-    #[test]
-    fn test_xpub_fixed_full_path() {
-        let key = "xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/1/2";
-        let ek = DescriptorExtendedKey::from_str(key).unwrap();
-        assert_eq!(ek.full_path(42), deriv_path!("m/1/2"));
-        assert_eq!(ek.full_path(1337), deriv_path!("m/1/2"));
-    }
-}
diff --git a/src/descriptor/keys.rs b/src/descriptor/keys.rs
deleted file mode 100644 (file)
index 7ff028d..0000000
+++ /dev/null
@@ -1,181 +0,0 @@
-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
-    }
-}
index 3000416b239ed0e25074cb7eec9469607f749a1c..103f9275f7c9e2bf7f1570ccefb912f011122b7c 100644 (file)
-use std::cell::RefCell;
-use std::collections::BTreeMap;
-use std::convert::{Into, TryFrom};
+use std::collections::{BTreeMap, HashMap};
 use std::fmt;
-use std::str::FromStr;
+use std::sync::Arc;
 
-use bitcoin::hashes::{hash160, Hash};
-use bitcoin::secp256k1::{All, Secp256k1};
-use bitcoin::util::bip32::{DerivationPath, ExtendedPrivKey, Fingerprint};
-use bitcoin::util::psbt::PartiallySignedTransaction as PSBT;
-use bitcoin::{PrivateKey, PublicKey, Script};
+use bitcoin::hashes::hash160;
+use bitcoin::secp256k1::Secp256k1;
+use bitcoin::util::bip32::{ChildNumber, DerivationPath, Fingerprint};
+use bitcoin::util::psbt;
+use bitcoin::{PublicKey, Script, TxOut};
 
+use miniscript::descriptor::{DescriptorPublicKey, DescriptorXKey, InnerXKey};
 pub use miniscript::{
-    Descriptor, Legacy, Miniscript, MiniscriptKey, ScriptContext, Segwitv0, Terminal,
+    Descriptor, Legacy, Miniscript, MiniscriptKey, ScriptContext, Segwitv0, Terminal, ToPublicKey,
 };
 
-use serde::{Deserialize, Serialize};
-
-use crate::psbt::utils::PSBTUtils;
-
 pub mod checksum;
 pub mod error;
-pub mod extended_key;
-mod keys;
 pub mod policy;
 
+// use crate::wallet::utils::AddressType;
+use crate::wallet::signer::SignersContainer;
+
 pub use self::checksum::get_checksum;
 use self::error::Error;
-pub use self::extended_key::{DerivationIndex, DescriptorExtendedKey};
 pub use self::policy::Policy;
 
-use self::keys::Key;
+pub type ExtendedDescriptor = Descriptor<DescriptorPublicKey>;
+type HDKeyPaths = BTreeMap<PublicKey, (Fingerprint, DerivationPath)>;
 
-trait MiniscriptExtractPolicy {
+pub trait ExtractPolicy {
     fn extract_policy(
         &self,
-        lookup_map: &BTreeMap<String, Box<dyn Key>>,
+        signers: Arc<SignersContainer<DescriptorPublicKey>>,
     ) -> Result<Option<Policy>, Error>;
 }
 
-pub trait ExtractPolicy {
-    fn extract_policy(&self) -> Result<Option<Policy>, Error>;
+pub trait XKeyUtils {
+    fn full_path(&self, append: &[ChildNumber]) -> DerivationPath;
+    fn root_fingerprint(&self) -> Fingerprint;
 }
 
-#[derive(Debug, Clone, Hash, PartialEq, PartialOrd, Eq, Ord, Default)]
-struct DummyKey();
+impl<K: InnerXKey> XKeyUtils for DescriptorXKey<K> {
+    fn full_path(&self, append: &[ChildNumber]) -> DerivationPath {
+        let full_path = match &self.source {
+            &Some((_, ref path)) => path
+                .into_iter()
+                .chain(self.derivation_path.into_iter())
+                .cloned()
+                .collect(),
+            &None => self.derivation_path.clone(),
+        };
 
-impl fmt::Display for DummyKey {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        write!(f, "DummyKey")
+        if self.is_wildcard {
+            full_path
+                .into_iter()
+                .chain(append.into_iter())
+                .cloned()
+                .collect()
+        } else {
+            full_path
+        }
     }
-}
-
-impl std::str::FromStr for DummyKey {
-    type Err = ();
 
-    fn from_str(_: &str) -> Result<Self, Self::Err> {
-        Ok(DummyKey::default())
+    fn root_fingerprint(&self) -> Fingerprint {
+        match &self.source {
+            &Some((fingerprint, _)) => fingerprint.clone(),
+            &None => self.xkey.xkey_fingerprint(),
+        }
     }
 }
 
-impl miniscript::MiniscriptKey for DummyKey {
-    type Hash = DummyKey;
-
-    fn to_pubkeyhash(&self) -> DummyKey {
-        DummyKey::default()
-    }
+pub trait DescriptorMeta: Sized {
+    fn is_witness(&self) -> bool;
+    fn get_hd_keypaths(&self, index: u32) -> Result<HDKeyPaths, Error>;
+    fn is_fixed(&self) -> bool;
+    fn derive_from_hd_keypaths(&self, hd_keypaths: &HDKeyPaths) -> Option<Self>;
+    fn derive_from_psbt_input(&self, psbt_input: &psbt::Input, utxo: Option<TxOut>)
+        -> Option<Self>;
+    // fn address_type(&self) -> Option<AddressType>;
 }
 
-pub type DerivedDescriptor = Descriptor<PublicKey>;
-pub type StringDescriptor = Descriptor<String>;
-
-pub trait DescriptorMeta {
-    fn is_witness(&self) -> bool;
+pub trait DescriptorScripts {
     fn psbt_redeem_script(&self) -> Option<Script>;
     fn psbt_witness_script(&self) -> Option<Script>;
 }
 
-impl<T> DescriptorMeta for Descriptor<T>
+impl<T> DescriptorScripts for Descriptor<T>
 where
     T: miniscript::MiniscriptKey + miniscript::ToPublicKey,
 {
-    fn is_witness(&self) -> bool {
-        match self {
-            Descriptor::Bare(_) | Descriptor::Pk(_) | Descriptor::Pkh(_) | Descriptor::Sh(_) => {
-                false
-            }
-            Descriptor::Wpkh(_)
-            | Descriptor::ShWpkh(_)
-            | Descriptor::Wsh(_)
-            | Descriptor::ShWsh(_) => true,
-        }
-    }
-
     fn psbt_redeem_script(&self) -> Option<Script> {
         match self {
             Descriptor::ShWpkh(_) => Some(self.witness_script()),
             Descriptor::ShWsh(ref script) => Some(script.encode().to_v0_p2wsh()),
             Descriptor::Sh(ref script) => Some(script.encode()),
+            Descriptor::Bare(ref script) => Some(script.encode()),
             _ => None,
         }
     }
@@ -110,305 +107,193 @@ where
     }
 }
 
-#[serde(try_from = "&str", into = "String")]
-#[derive(Debug, Serialize, Deserialize)]
-pub struct ExtendedDescriptor {
-    #[serde(flatten)]
-    internal: StringDescriptor,
-
-    #[serde(skip)]
-    keys: BTreeMap<String, Box<dyn Key>>,
-
-    #[serde(skip)]
-    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 {
-    fn clone(&self) -> Self {
-        Self {
-            internal: self.internal.clone(),
-            ctx: self.ctx.clone(),
-            keys: BTreeMap::new(),
-        }
-    }
-}
-
-impl std::convert::AsRef<StringDescriptor> for ExtendedDescriptor {
-    fn as_ref(&self) -> &StringDescriptor {
-        &self.internal
-    }
-}
-
-impl ExtendedDescriptor {
-    fn parse_string(string: &str) -> Result<(String, Box<dyn Key>), Error> {
-        if let Ok(pk) = PublicKey::from_str(string) {
-            return Ok((string.to_string(), Box::new(pk)));
-        } else if let Ok(sk) = PrivateKey::from_wif(string) {
-            return Ok((string.to_string(), Box::new(sk)));
-        } else if let Ok(ext_key) = DescriptorExtendedKey::from_str(string) {
-            return Ok((string.to_string(), Box::new(ext_key)));
+impl DescriptorMeta for Descriptor<DescriptorPublicKey> {
+    fn is_witness(&self) -> bool {
+        match self {
+            Descriptor::Bare(_) | Descriptor::Pk(_) | Descriptor::Pkh(_) | Descriptor::Sh(_) => {
+                false
+            }
+            Descriptor::Wpkh(_)
+            | Descriptor::ShWpkh(_)
+            | Descriptor::Wsh(_)
+            | Descriptor::ShWsh(_) => true,
         }
-
-        return Err(Error::KeyParsingError(string.to_string()));
     }
 
-    fn new(sd: StringDescriptor) -> Result<Self, Error> {
-        let ctx = Secp256k1::gen_new();
-        let keys: RefCell<BTreeMap<String, Box<dyn Key>>> = RefCell::new(BTreeMap::new());
-
-        let translatefpk = |string: &String| -> Result<_, Error> {
-            let (key, parsed) = Self::parse_string(string)?;
-            keys.borrow_mut().insert(key, parsed);
+    fn get_hd_keypaths(&self, index: u32) -> Result<HDKeyPaths, Error> {
+        let mut answer = BTreeMap::new();
 
-            Ok(DummyKey::default())
-        };
-        let translatefpkh = |string: &String| -> Result<_, Error> {
-            let (key, parsed) = Self::parse_string(string)?;
-            keys.borrow_mut().insert(key, parsed);
+        let translatefpk = |key: &DescriptorPublicKey| -> Result<_, Error> {
+            match key {
+                DescriptorPublicKey::PubKey(_) => {}
+                DescriptorPublicKey::XPub(xpub) => {
+                    let derive_path = if xpub.is_wildcard {
+                        xpub.derivation_path
+                            .into_iter()
+                            .chain([ChildNumber::from_normal_idx(index)?].iter())
+                            .cloned()
+                            .collect()
+                    } else {
+                        xpub.derivation_path.clone()
+                    };
+                    let derived_pubkey = xpub
+                        .xkey
+                        .derive_pub(&Secp256k1::verification_only(), &derive_path)?;
+
+                    answer.insert(
+                        derived_pubkey.public_key,
+                        (
+                            xpub.root_fingerprint(),
+                            xpub.full_path(&[ChildNumber::from_normal_idx(index)?]),
+                        ),
+                    );
+                }
+            }
 
             Ok(DummyKey::default())
         };
+        let translatefpkh = |_: &hash160::Hash| -> Result<_, Error> { Ok(DummyKey::default()) };
 
-        sd.translate_pk(translatefpk, translatefpkh)?;
+        self.translate_pk(translatefpk, translatefpkh)?;
 
-        Ok(ExtendedDescriptor {
-            internal: sd,
-            keys: keys.into_inner(),
-            ctx,
-        })
+        Ok(answer)
     }
 
-    pub fn derive_with_miniscript_legacy(
-        &self,
-        miniscript: Miniscript<PublicKey, Legacy>,
-    ) -> Result<DerivedDescriptor, Error> {
-        let derived_desc = match self.internal {
-            Descriptor::Bare(_) => Descriptor::Bare(miniscript),
-            Descriptor::Sh(_) => Descriptor::Sh(miniscript),
-            _ => return Err(Error::CantDeriveWithMiniscript),
-        };
+    fn is_fixed(&self) -> bool {
+        let mut found_wildcard = false;
 
-        // if !self.same_structure(&derived_desc) {
-        //     Err(Error::CantDeriveWithMiniscript)
-        // } else {
-        Ok(derived_desc)
-        // }
-    }
+        let translatefpk = |key: &DescriptorPublicKey| -> Result<_, Error> {
+            match key {
+                DescriptorPublicKey::PubKey(_) => {}
+                DescriptorPublicKey::XPub(xpub) => {
+                    if xpub.is_wildcard {
+                        found_wildcard = true;
+                    }
+                }
+            }
 
-    pub fn derive_with_miniscript_segwit_v0(
-        &self,
-        miniscript: Miniscript<PublicKey, Segwitv0>,
-    ) -> Result<DerivedDescriptor, Error> {
-        let derived_desc = match self.internal {
-            Descriptor::Wsh(_) => Descriptor::Wsh(miniscript),
-            Descriptor::ShWsh(_) => Descriptor::ShWsh(miniscript),
-            _ => return Err(Error::CantDeriveWithMiniscript),
+            Ok(DummyKey::default())
         };
+        let translatefpkh = |_: &hash160::Hash| -> Result<_, Error> { Ok(DummyKey::default()) };
+
+        self.translate_pk(translatefpk, translatefpkh).unwrap();
 
-        // if !self.same_structure(&derived_desc) {
-        //     Err(Error::CantDeriveWithMiniscript)
-        // } else {
-        Ok(derived_desc)
-        // }
+        !found_wildcard
     }
 
-    pub fn derive_from_psbt_input(
-        &self,
-        psbt: &PSBT,
-        input_index: usize,
-    ) -> Result<DerivedDescriptor, Error> {
-        let get_pk_from_partial_sigs = || {
-            // here we need the public key.. since it's a single sig, there are only two
-            // options: we can either find it in the `partial_sigs`, or we can't. if we
-            // can't, it means that we can't even satisfy the input, so we can exit knowing
-            // that we did our best to try to find it.
-            psbt.inputs[input_index]
-                .partial_sigs
-                .keys()
-                .nth(0)
-                .ok_or(Error::MissingPublicKey)
-        };
+    fn derive_from_hd_keypaths(&self, hd_keypaths: &HDKeyPaths) -> Option<Self> {
+        let index: HashMap<_, _> = hd_keypaths.values().cloned().collect();
 
-        if let Some(wit_script) = &psbt.inputs[input_index].witness_script {
-            self.derive_with_miniscript_segwit_v0(Miniscript::parse(wit_script)?)
-        } else if let Some(p2sh_script) = &psbt.inputs[input_index].redeem_script {
-            if p2sh_script.is_v0_p2wpkh() {
-                // wrapped p2wpkh
-                get_pk_from_partial_sigs().map(|pk| Descriptor::ShWpkh(*pk))
-            } else {
-                self.derive_with_miniscript_legacy(Miniscript::parse(p2sh_script)?)
+        let mut derive_path = None::<DerivationPath>;
+        let translatefpk = |key: &DescriptorPublicKey| -> Result<_, Error> {
+            if derive_path.is_some() {
+                // already found a matching path, we are done
+                return Ok(DummyKey::default());
             }
-        } else if let Some(utxo) = psbt.get_utxo_for(input_index) {
-            if utxo.script_pubkey.is_p2pkh() {
-                get_pk_from_partial_sigs().map(|pk| Descriptor::Pkh(*pk))
-            } else if utxo.script_pubkey.is_p2pk() {
-                get_pk_from_partial_sigs().map(|pk| Descriptor::Pk(*pk))
-            } else if utxo.script_pubkey.is_v0_p2wpkh() {
-                get_pk_from_partial_sigs().map(|pk| Descriptor::Wpkh(*pk))
-            } else {
-                // try as bare script
-                self.derive_with_miniscript_legacy(Miniscript::parse(&utxo.script_pubkey)?)
+
+            if let DescriptorPublicKey::XPub(xpub) = key {
+                // Check if the key matches one entry in our `index`. If it does, `matches()` will
+                // return the "prefix" that matched, so we remove that prefix from the full path
+                // found in `index` and save it in `derive_path`
+                let root_fingerprint = xpub.root_fingerprint();
+                derive_path = index
+                    .get_key_value(&root_fingerprint)
+                    .and_then(|(fingerprint, path)| xpub.matches(*fingerprint, path))
+                    .map(|prefix_path| prefix_path.into_iter().cloned().collect::<Vec<_>>())
+                    .map(|prefix| {
+                        index
+                            .get(&xpub.root_fingerprint())
+                            .unwrap()
+                            .into_iter()
+                            .skip(prefix.len())
+                            .cloned()
+                            .collect()
+                    });
             }
-        } else {
-            Err(Error::MissingDetails)
-        }
-    }
 
-    pub fn derive(&self, index: u32) -> Result<DerivedDescriptor, Error> {
-        let translatefpk = |xpub: &String| {
-            self.keys
-                .get(xpub)
-                .unwrap()
-                .as_public_key(&self.ctx, Some(index))
+            Ok(DummyKey::default())
         };
-        let translatefpkh =
-            |xpub: &String| Ok(hash160::Hash::hash(&translatefpk(xpub)?.to_bytes()));
+        let translatefpkh = |_: &hash160::Hash| -> Result<_, Error> { Ok(DummyKey::default()) };
 
-        Ok(self.internal.translate_pk(translatefpk, translatefpkh)?)
-    }
+        self.translate_pk(translatefpk, translatefpkh).unwrap();
 
-    pub fn get_xprv(&self) -> impl IntoIterator<Item = ExtendedPrivKey> + '_ {
-        self.keys
-            .iter()
-            .filter(|(_, v)| v.xprv().is_some())
-            .map(|(_, v)| v.xprv().unwrap())
+        derive_path.map(|path| self.derive(path.as_ref()))
     }
 
-    pub fn get_secret_keys(&self) -> impl IntoIterator<Item = PrivateKey> + '_ {
-        self.keys
-            .iter()
-            .filter(|(_, v)| v.as_secret_key().is_some())
-            .map(|(_, v)| v.as_secret_key().unwrap())
-    }
-
-    pub fn get_hd_keypaths(
+    fn derive_from_psbt_input(
         &self,
-        index: u32,
-    ) -> Result<BTreeMap<PublicKey, (Fingerprint, DerivationPath)>, Error> {
-        let mut answer = BTreeMap::new();
-
-        for (_, key) in &self.keys {
-            if let Some(fingerprint) = key.fingerprint(&self.ctx) {
-                let derivation_path = key.full_path(index).unwrap();
-                let pubkey = key.as_public_key(&self.ctx, Some(index))?;
-
-                answer.insert(pubkey, (fingerprint, derivation_path));
-            }
+        psbt_input: &psbt::Input,
+        utxo: Option<TxOut>,
+    ) -> Option<Self> {
+        if let Some(derived) = self.derive_from_hd_keypaths(&psbt_input.hd_keypaths) {
+            return Some(derived);
+        } else if !self.is_fixed() {
+            // If the descriptor is not fixed we can't brute-force the derivation address, so just
+            // exit here
+            return None;
         }
 
-        Ok(answer)
-    }
-
-    pub fn max_satisfaction_weight(&self) -> usize {
-        let fake_pk = PublicKey::from_slice(&[
-            2, 140, 40, 169, 123, 248, 41, 139, 192, 210, 61, 140, 116, 148, 82, 163, 46, 105, 75,
-            101, 227, 10, 148, 114, 163, 149, 74, 179, 15, 229, 50, 76, 170,
-        ])
-        .unwrap();
-        let translated: Descriptor<PublicKey> = self
-            .internal
-            .translate_pk(
-                |_| -> Result<_, ()> { Ok(fake_pk.clone()) },
-                |_| -> Result<_, ()> { Ok(Default::default()) },
-            )
-            .unwrap();
-
-        translated.max_satisfaction_weight()
-    }
-
-    pub fn is_fixed(&self) -> bool {
-        self.keys.iter().all(|(_, key)| key.is_fixed())
-    }
-
-    pub fn same_structure<K: MiniscriptKey>(&self, other: &Descriptor<K>) -> bool {
-        // Translate all the public keys to () and then check if the two descriptors are equal.
-        // TODO: translate hashes to their default value before checking for ==
-
-        let func_string = |_string: &String| -> Result<_, Error> { Ok(DummyKey::default()) };
-
-        let func_generic_pk = |_data: &K| -> Result<_, Error> { Ok(DummyKey::default()) };
-        let func_generic_pkh =
-            |_data: &<K as MiniscriptKey>::Hash| -> Result<_, Error> { Ok(DummyKey::default()) };
-
-        let translated_a = self.internal.translate_pk(func_string, func_string);
-        let translated_b = other.translate_pk(func_generic_pk, func_generic_pkh);
-
-        match (translated_a, translated_b) {
-            (Ok(a), Ok(b)) => a == b,
-            _ => false,
+        match self {
+            Descriptor::Pk(_)
+            | Descriptor::Pkh(_)
+            | Descriptor::Wpkh(_)
+            | Descriptor::ShWpkh(_)
+                if utxo.is_some()
+                    && self.script_pubkey() == utxo.as_ref().unwrap().script_pubkey =>
+            {
+                Some(self.clone())
+            }
+            Descriptor::Bare(ms) | Descriptor::Sh(ms)
+                if psbt_input.redeem_script.is_some()
+                    && &ms.encode() == psbt_input.redeem_script.as_ref().unwrap() =>
+            {
+                Some(self.clone())
+            }
+            Descriptor::Wsh(ms) | Descriptor::ShWsh(ms)
+                if psbt_input.witness_script.is_some()
+                    && &ms.encode() == psbt_input.witness_script.as_ref().unwrap() =>
+            {
+                Some(self.clone())
+            }
+            _ => None,
         }
     }
 
-    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 {
-    fn extract_policy(&self) -> Result<Option<Policy>, Error> {
-        self.internal.extract_policy(&self.keys)
-    }
+    // fn address_type(&self) -> Option<AddressType> {
+    //     match self {
+    //         Descriptor::Pkh(_) => Some(AddressType::Pkh),
+    //         Descriptor::Wpkh(_) => Some(AddressType::Wpkh),
+    //         Descriptor::ShWpkh(_) => Some(AddressType::ShWpkh),
+    //         Descriptor::Sh(_) => Some(AddressType::Sh),
+    //         Descriptor::Wsh(_) => Some(AddressType::Wsh),
+    //         Descriptor::ShWsh(_) => Some(AddressType::ShWsh),
+    //         _ => None,
+    //     }
+    // }
 }
 
-impl TryFrom<&str> for ExtendedDescriptor {
-    type Error = Error;
+#[derive(Debug, Clone, Hash, PartialEq, PartialOrd, Eq, Ord, Default)]
+struct DummyKey();
 
-    fn try_from(value: &str) -> Result<Self, Self::Error> {
-        let internal = StringDescriptor::from_str(value)?;
-        ExtendedDescriptor::new(internal)
+impl fmt::Display for DummyKey {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(f, "DummyKey")
     }
 }
 
-impl TryFrom<StringDescriptor> for ExtendedDescriptor {
-    type Error = Error;
+impl std::str::FromStr for DummyKey {
+    type Err = ();
 
-    fn try_from(other: StringDescriptor) -> Result<Self, Self::Error> {
-        ExtendedDescriptor::new(other)
+    fn from_str(_: &str) -> Result<Self, Self::Err> {
+        Ok(DummyKey::default())
     }
 }
 
-impl FromStr for ExtendedDescriptor {
-    type Err = Error;
-
-    fn from_str(s: &str) -> Result<Self, Self::Err> {
-        Self::try_from(s)
-    }
-}
+impl miniscript::MiniscriptKey for DummyKey {
+    type Hash = DummyKey;
 
-impl Into<String> for ExtendedDescriptor {
-    fn into(self) -> String {
-        format!("{}", self.internal)
+    fn to_pubkeyhash(&self) -> DummyKey {
+        DummyKey::default()
     }
 }
 
@@ -416,131 +301,23 @@ impl Into<String> for ExtendedDescriptor {
 mod test {
     use std::str::FromStr;
 
+    use bitcoin::consensus::encode::deserialize;
     use bitcoin::hashes::hex::FromHex;
-    use bitcoin::{Network, PublicKey};
+    use bitcoin::util::psbt;
 
-    use crate::descriptor::*;
-
-    macro_rules! hex_fingerprint {
-        ($hex:expr) => {
-            Fingerprint::from_hex($hex).unwrap()
-        };
-    }
-
-    macro_rules! hex_pubkey {
-        ($hex:expr) => {
-            PublicKey::from_str($hex).unwrap()
-        };
-    }
-
-    macro_rules! deriv_path {
-        ($str:expr) => {
-            DerivationPath::from_str($str).unwrap()
-        };
-
-        () => {
-            DerivationPath::from(vec![])
-        };
-    }
-
-    #[test]
-    fn test_descriptor_parse_wif() {
-        let string = "pkh(cVt4o7BGAig1UXywgGSmARhxMdzP5qvQsxKkSsc1XEkw3tDTQFpy)";
-        let desc = ExtendedDescriptor::from_str(string).unwrap();
-        assert!(desc.is_fixed());
-        assert_eq!(
-            desc.derive(0)
-                .unwrap()
-                .address(Network::Testnet)
-                .unwrap()
-                .to_string(),
-            "mqwpxxvfv3QbM8PU8uBx2jaNt9btQqvQNx"
-        );
-        assert_eq!(
-            desc.derive(42)
-                .unwrap()
-                .address(Network::Testnet)
-                .unwrap()
-                .to_string(),
-            "mqwpxxvfv3QbM8PU8uBx2jaNt9btQqvQNx"
-        );
-        assert_eq!(
-            desc.get_secret_keys().into_iter().collect::<Vec<_>>().len(),
-            1
-        );
-    }
+    use super::*;
+    use crate::psbt::PSBTUtils;
 
     #[test]
-    fn test_descriptor_parse_pubkey() {
-        let string = "pkh(039b6347398505f5ec93826dc61c19f47c66c0283ee9be980e29ce325a0f4679ef)";
-        let desc = ExtendedDescriptor::from_str(string).unwrap();
-        assert!(desc.is_fixed());
-        assert_eq!(
-            desc.derive(0)
-                .unwrap()
-                .address(Network::Testnet)
-                .unwrap()
-                .to_string(),
-            "mqwpxxvfv3QbM8PU8uBx2jaNt9btQqvQNx"
-        );
-        assert_eq!(
-            desc.derive(42)
-                .unwrap()
-                .address(Network::Testnet)
-                .unwrap()
-                .to_string(),
-            "mqwpxxvfv3QbM8PU8uBx2jaNt9btQqvQNx"
-        );
-        assert_eq!(
-            desc.get_secret_keys().into_iter().collect::<Vec<_>>().len(),
-            0
-        );
-    }
-
-    #[test]
-    fn test_descriptor_parse_xpub() {
-        let string = "pkh(tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/*)";
-        let desc = ExtendedDescriptor::from_str(string).unwrap();
-        assert!(!desc.is_fixed());
-        assert_eq!(
-            desc.derive(0)
-                .unwrap()
-                .address(Network::Testnet)
-                .unwrap()
-                .to_string(),
-            "mxbXpnVkwARGtYXk5yeGYf59bGWuPpdE4X"
-        );
-        assert_eq!(
-            desc.derive(42)
-                .unwrap()
-                .address(Network::Testnet)
-                .unwrap()
-                .to_string(),
-            "mhtuS1QaEV4HPcK4bWk4Wvpd64SUjiC5Zt"
-        );
-        assert_eq!(desc.get_xprv().into_iter().collect::<Vec<_>>().len(), 0);
-    }
-
-    #[test]
-    #[should_panic(expected = "KeyParsingError")]
-    fn test_descriptor_parse_fail() {
-        let string = "pkh(this_is_not_a_valid_key)";
-        ExtendedDescriptor::from_str(string).unwrap();
-    }
+    fn test_derive_from_psbt_input_wpkh() {
+        let psbt: psbt::PartiallySignedTransaction = deserialize(&Vec::<u8>::from_hex("70736274ff010052010000000162307be8e431fbaff807cdf9cdc3fde44d740211bc8342c31ffd6ec11fe35bcc0100000000ffffffff01328601000000000016001493ce48570b55c42c2af816aeaba06cfee1224fae000000000001011fa08601000000000016001493ce48570b55c42c2af816aeaba06cfee1224fae010304010000000000").unwrap()).unwrap();
 
-    #[test]
-    fn test_descriptor_hd_keypaths() {
-        let string = "pkh(tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/*)";
-        let desc = ExtendedDescriptor::from_str(string).unwrap();
-        let keypaths = desc.get_hd_keypaths(0).unwrap();
-        assert!(keypaths.contains_key(&hex_pubkey!(
-            "025d5fc65ebb8d44a5274b53bac21ff8307fec2334a32df05553459f8b1f7fe1b6"
-        )));
-        assert_eq!(
-            keypaths.get(&hex_pubkey!(
-                "025d5fc65ebb8d44a5274b53bac21ff8307fec2334a32df05553459f8b1f7fe1b6"
-            )),
-            Some(&(hex_fingerprint!("31a507b8"), deriv_path!("m/0")))
+        let descriptor = Descriptor::<DescriptorPublicKey>::from_str(
+            "wpkh(02b4632d08485ff1df2db55b9dafd23347d1c47a457072a1e87be26896549a8737)",
         )
+        .unwrap();
+
+        let result = descriptor.derive_from_psbt_input(&psbt.inputs[0], psbt.get_utxo_for(0));
+        println!("{:?}", result);
     }
 }
index 13158cb91a487f70650e9727048497fcf77f26e2..24ccf4b6caf99e0043406689c79918a4510d5cb1 100644 (file)
@@ -1,23 +1,26 @@
 use std::cmp::max;
 use std::collections::{BTreeMap, HashSet, VecDeque};
+use std::sync::Arc;
 
 use serde::ser::SerializeMap;
 use serde::{Serialize, Serializer};
 
 use bitcoin::hashes::*;
-use bitcoin::secp256k1::Secp256k1;
 use bitcoin::util::bip32::Fingerprint;
 use bitcoin::PublicKey;
 
-use miniscript::{Descriptor, Miniscript, ScriptContext, Terminal};
+use miniscript::descriptor::DescriptorPublicKey;
+use miniscript::{Descriptor, Miniscript, MiniscriptKey, ScriptContext, Terminal};
 
 #[allow(unused_imports)]
 use log::{debug, error, info, trace};
 
+use crate::descriptor::ExtractPolicy;
+use crate::wallet::signer::{SignerId, SignersContainer};
+
 use super::checksum::get_checksum;
 use super::error::Error;
-use crate::descriptor::{Key, MiniscriptExtractPolicy};
-use crate::psbt::PSBTSatisfier;
+use super::XKeyUtils;
 
 #[derive(Debug, Clone, Default, Serialize)]
 pub struct PKOrF {
@@ -30,20 +33,23 @@ pub struct PKOrF {
 }
 
 impl PKOrF {
-    fn from_key(k: &Box<dyn Key>) -> Self {
-        let secp = Secp256k1::gen_new();
-
-        let pubkey = k.as_public_key(&secp, None).unwrap();
-        if let Some(fing) = k.fingerprint(&secp) {
-            PKOrF {
-                fingerprint: Some(fing),
+    fn from_key(k: &DescriptorPublicKey) -> Self {
+        match k {
+            DescriptorPublicKey::PubKey(pubkey) => PKOrF {
+                pubkey: Some(*pubkey),
                 ..Default::default()
-            }
-        } else {
-            PKOrF {
-                pubkey: Some(pubkey),
+            },
+            DescriptorPublicKey::XPub(xpub) => PKOrF {
+                fingerprint: Some(xpub.root_fingerprint()),
                 ..Default::default()
-            }
+            },
+        }
+    }
+
+    fn from_key_hash(k: hash160::Hash) -> Self {
+        PKOrF {
+            pubkey_hash: Some(k),
+            ..Default::default()
         }
     }
 }
@@ -445,14 +451,15 @@ impl Policy {
     }
 
     fn make_multisig(
-        keys: Vec<Option<&Box<dyn Key>>>,
+        keys: &Vec<DescriptorPublicKey>,
+        signers: Arc<SignersContainer<DescriptorPublicKey>>,
         threshold: usize,
     ) -> Result<Option<Policy>, PolicyError> {
         if threshold == 0 {
             return Ok(None);
         }
 
-        let parsed_keys = keys.iter().map(|k| PKOrF::from_key(k.unwrap())).collect();
+        let parsed_keys = keys.iter().map(|k| PKOrF::from_key(k)).collect();
 
         let mut contribution = Satisfaction::Partial {
             n: keys.len(),
@@ -461,14 +468,14 @@ impl Policy {
             conditions: Default::default(),
         };
         for (index, key) in keys.iter().enumerate() {
-            let val = if key.is_some() && key.unwrap().has_secret() {
-                Satisfaction::Complete {
-                    condition: Default::default(),
-                }
-            } else {
-                Satisfaction::None
-            };
-            contribution.add(&val, index)?;
+            if let Some(_) = signers.find(signer_id(key)) {
+                contribution.add(
+                    &Satisfaction::Complete {
+                        condition: Default::default(),
+                    },
+                    index,
+                )?;
+            }
         }
         contribution.finalize()?;
 
@@ -482,15 +489,6 @@ impl Policy {
         Ok(Some(policy))
     }
 
-    pub fn satisfy<Ctx: ScriptContext>(
-        &mut self,
-        _satisfier: &PSBTSatisfier,
-        _desc_node: &Terminal<PublicKey, Ctx>,
-    ) {
-        //self.satisfaction = self.item.satisfy(satisfier, desc_node);
-        //self.contribution += &self.satisfaction;
-    }
-
     pub fn requires_path(&self) -> bool {
         self.get_requirements(&BTreeMap::new()).is_err()
     }
@@ -566,60 +564,55 @@ impl From<SatisfiableItem> for Policy {
     }
 }
 
-fn signature_from_string(key: Option<&Box<dyn Key>>) -> Option<Policy> {
-    key.map(|k| {
-        let mut policy: Policy = SatisfiableItem::Signature(PKOrF::from_key(k)).into();
-        policy.contribution = if k.has_secret() {
-            Satisfaction::Complete {
-                condition: Default::default(),
-            }
-        } else {
-            Satisfaction::None
-        };
+fn signer_id(key: &DescriptorPublicKey) -> SignerId<DescriptorPublicKey> {
+    match key {
+        DescriptorPublicKey::PubKey(pubkey) => pubkey.to_pubkeyhash().into(),
+        DescriptorPublicKey::XPub(xpub) => xpub.root_fingerprint().into(),
+    }
+}
 
-        policy
-    })
+fn signature(
+    key: &DescriptorPublicKey,
+    signers: Arc<SignersContainer<DescriptorPublicKey>>,
+) -> Policy {
+    let mut policy: Policy = SatisfiableItem::Signature(PKOrF::from_key(key)).into();
+
+    policy.contribution = if signers.find(signer_id(key)).is_some() {
+        Satisfaction::Complete {
+            condition: Default::default(),
+        }
+    } else {
+        Satisfaction::None
+    };
+
+    policy
 }
 
-fn signature_key_from_string(key: Option<&Box<dyn Key>>) -> Option<Policy> {
-    let secp = Secp256k1::gen_new();
+fn signature_key(
+    key_hash: &<DescriptorPublicKey as MiniscriptKey>::Hash,
+    signers: Arc<SignersContainer<DescriptorPublicKey>>,
+) -> Policy {
+    let mut policy: Policy = SatisfiableItem::Signature(PKOrF::from_key_hash(*key_hash)).into();
 
-    key.map(|k| {
-        let pubkey = k.as_public_key(&secp, None).unwrap();
-        let mut policy: Policy = if let Some(fing) = k.fingerprint(&secp) {
-            SatisfiableItem::SignatureKey(PKOrF {
-                fingerprint: Some(fing),
-                ..Default::default()
-            })
-        } else {
-            SatisfiableItem::SignatureKey(PKOrF {
-                pubkey_hash: Some(hash160::Hash::hash(&pubkey.to_bytes())),
-                ..Default::default()
-            })
+    if let Some(_) = signers.find(SignerId::PkHash(*key_hash)) {
+        policy.contribution = Satisfaction::Complete {
+            condition: Default::default(),
         }
-        .into();
-        policy.contribution = if k.has_secret() {
-            Satisfaction::Complete {
-                condition: Default::default(),
-            }
-        } else {
-            Satisfaction::None
-        };
+    }
 
-        policy
-    })
+    policy
 }
 
-impl<Ctx: ScriptContext> MiniscriptExtractPolicy for Miniscript<String, Ctx> {
+impl<Ctx: ScriptContext> ExtractPolicy for Miniscript<DescriptorPublicKey, Ctx> {
     fn extract_policy(
         &self,
-        lookup_map: &BTreeMap<String, Box<dyn Key>>,
+        signers: Arc<SignersContainer<DescriptorPublicKey>>,
     ) -> Result<Option<Policy>, Error> {
         Ok(match &self.node {
             // Leaves
             Terminal::True | Terminal::False => None,
-            Terminal::PkK(pubkey) => signature_from_string(lookup_map.get(pubkey)),
-            Terminal::PkH(pubkey_hash) => signature_key_from_string(lookup_map.get(pubkey_hash)),
+            Terminal::PkK(pubkey) => Some(signature(pubkey, Arc::clone(&signers))),
+            Terminal::PkH(pubkey_hash) => Some(signature_key(pubkey_hash, Arc::clone(&signers))),
             Terminal::After(value) => {
                 let mut policy: Policy = SatisfiableItem::AbsoluteTimelock { value: *value }.into();
                 policy.contribution = Satisfaction::Complete {
@@ -652,9 +645,7 @@ impl<Ctx: ScriptContext> MiniscriptExtractPolicy for Miniscript<String, Ctx> {
             Terminal::Hash160(hash) => {
                 Some(SatisfiableItem::HASH160Preimage { hash: *hash }.into())
             }
-            Terminal::Multi(k, pks) => {
-                Policy::make_multisig(pks.iter().map(|s| lookup_map.get(s)).collect(), *k)?
-            }
+            Terminal::Multi(k, pks) => Policy::make_multisig(pks, Arc::clone(&signers), *k)?,
             // Identities
             Terminal::Alt(inner)
             | Terminal::Swap(inner)
@@ -662,26 +653,31 @@ impl<Ctx: ScriptContext> MiniscriptExtractPolicy for Miniscript<String, Ctx> {
             | Terminal::DupIf(inner)
             | Terminal::Verify(inner)
             | Terminal::NonZero(inner)
-            | Terminal::ZeroNotEqual(inner) => inner.extract_policy(lookup_map)?,
+            | Terminal::ZeroNotEqual(inner) => inner.extract_policy(Arc::clone(&signers))?,
             // Complex policies
-            Terminal::AndV(a, b) | Terminal::AndB(a, b) => {
-                Policy::make_and(a.extract_policy(lookup_map)?, b.extract_policy(lookup_map)?)?
-            }
+            Terminal::AndV(a, b) | Terminal::AndB(a, b) => Policy::make_and(
+                a.extract_policy(Arc::clone(&signers))?,
+                b.extract_policy(Arc::clone(&signers))?,
+            )?,
             Terminal::AndOr(x, y, z) => Policy::make_or(
-                Policy::make_and(x.extract_policy(lookup_map)?, y.extract_policy(lookup_map)?)?,
-                z.extract_policy(lookup_map)?,
+                Policy::make_and(
+                    x.extract_policy(Arc::clone(&signers))?,
+                    y.extract_policy(Arc::clone(&signers))?,
+                )?,
+                z.extract_policy(Arc::clone(&signers))?,
             )?,
             Terminal::OrB(a, b)
             | Terminal::OrD(a, b)
             | Terminal::OrC(a, b)
-            | Terminal::OrI(a, b) => {
-                Policy::make_or(a.extract_policy(lookup_map)?, b.extract_policy(lookup_map)?)?
-            }
+            | Terminal::OrI(a, b) => Policy::make_or(
+                a.extract_policy(Arc::clone(&signers))?,
+                b.extract_policy(Arc::clone(&signers))?,
+            )?,
             Terminal::Thresh(k, nodes) => {
                 let mut threshold = *k;
                 let mapped: Vec<_> = nodes
                     .iter()
-                    .map(|n| n.extract_policy(lookup_map))
+                    .map(|n| n.extract_policy(Arc::clone(&signers)))
                     .collect::<Result<Vec<_>, _>>()?
                     .into_iter()
                     .filter_map(|x| x)
@@ -700,22 +696,18 @@ impl<Ctx: ScriptContext> MiniscriptExtractPolicy for Miniscript<String, Ctx> {
     }
 }
 
-impl MiniscriptExtractPolicy for Descriptor<String> {
+impl ExtractPolicy for Descriptor<DescriptorPublicKey> {
     fn extract_policy(
         &self,
-        lookup_map: &BTreeMap<String, Box<dyn Key>>,
+        signers: Arc<SignersContainer<DescriptorPublicKey>>,
     ) -> Result<Option<Policy>, Error> {
         match self {
             Descriptor::Pk(pubkey)
             | Descriptor::Pkh(pubkey)
             | Descriptor::Wpkh(pubkey)
-            | Descriptor::ShWpkh(pubkey) => Ok(signature_from_string(lookup_map.get(pubkey))),
-            Descriptor::Bare(inner) | Descriptor::Sh(inner) => {
-                Ok(inner.extract_policy(lookup_map)?)
-            }
-            Descriptor::Wsh(inner) | Descriptor::ShWsh(inner) => {
-                Ok(inner.extract_policy(lookup_map)?)
-            }
+            | Descriptor::ShWpkh(pubkey) => Ok(Some(signature(pubkey, signers))),
+            Descriptor::Bare(inner) | Descriptor::Sh(inner) => Ok(inner.extract_policy(signers)?),
+            Descriptor::Wsh(inner) | Descriptor::ShWsh(inner) => Ok(inner.extract_policy(signers)?),
         }
     }
 }
index 77cb92c38f400bbac74d2da3ad0ef93a8a1f8b64..f1631bf023bcd3e70f93effbdba450a08b77b0b1 100644 (file)
@@ -1,4 +1,4 @@
-use bitcoin::{Address, OutPoint, Script, Txid};
+use bitcoin::{Address, OutPoint};
 
 #[derive(Debug)]
 pub enum Error {
@@ -25,13 +25,7 @@ pub enum Error {
     SpendingPolicyRequired,
     InvalidPolicyPathError(crate::descriptor::policy::PolicyError),
 
-    // Signing errors (expected, received)
-    InputTxidMismatch((Txid, OutPoint)),
-    InputRedeemScriptMismatch((Script, Script)), // scriptPubKey, redeemScript
-    InputWitnessScriptMismatch((Script, Script)), // scriptPubKey, redeemScript
-    InputUnknownSegwitScript(Script),
-    InputMissingWitnessScript(usize),
-    MissingUTXO,
+    Signer(crate::wallet::signer::SignerError),
 
     // Blockchain interface errors
     Uncapable(crate::blockchain::Capability),
@@ -44,6 +38,7 @@ pub enum Error {
     Descriptor(crate::descriptor::error::Error),
 
     Encode(bitcoin::consensus::encode::Error),
+    Miniscript(miniscript::Error),
     BIP32(bitcoin::util::bip32::Error),
     Secp256k1(bitcoin::secp256k1::Error),
     JSON(serde_json::Error),
@@ -75,8 +70,10 @@ impl_error!(
     crate::descriptor::policy::PolicyError,
     InvalidPolicyPathError
 );
+impl_error!(crate::wallet::signer::SignerError, Signer);
 
 impl_error!(bitcoin::consensus::encode::Error, Encode);
+impl_error!(miniscript::Error, Miniscript);
 impl_error!(bitcoin::util::bip32::Error, BIP32);
 impl_error!(bitcoin::secp256k1::Error, Secp256k1);
 impl_error!(serde_json::Error, JSON);
index f727c10996fb74e46110a66dc9aa50d436cf4b3b..b3f8f8440625fc81f4fc09b9ae4c3afb56c1de13 100644 (file)
@@ -47,7 +47,6 @@ pub mod blockchain;
 pub mod database;
 pub mod descriptor;
 pub mod psbt;
-pub mod signer;
 pub mod types;
 pub mod wallet;
 
index 1db458b5242944e4fac9725c3564db66217d8991..35552c2587820cbba2ba6d528ca7c17b70acb980 100644 (file)
-use std::collections::BTreeMap;
+use bitcoin::util::psbt::PartiallySignedTransaction as PSBT;
+use bitcoin::TxOut;
 
-use bitcoin::hashes::{hash160, Hash};
-use bitcoin::util::bip143::SighashComponents;
-use bitcoin::util::bip32::{DerivationPath, ExtendedPrivKey, Fingerprint};
-use bitcoin::util::psbt;
-use bitcoin::{PrivateKey, PublicKey, Script, SigHashType, Transaction};
-
-use bitcoin::secp256k1::{self, All, Message, Secp256k1};
-
-#[allow(unused_imports)]
-use log::{debug, error, info, trace};
-
-use miniscript::{BitcoinSig, MiniscriptKey, Satisfier};
-
-use crate::descriptor::ExtendedDescriptor;
-use crate::error::Error;
-use crate::signer::Signer;
-
-pub mod utils;
-
-pub struct PSBTSatisfier<'a> {
-    input: &'a psbt::Input,
-    assume_height_reached: bool,
-    create_height: Option<u32>,
-    current_height: Option<u32>,
-}
-
-impl<'a> PSBTSatisfier<'a> {
-    pub fn new(
-        input: &'a psbt::Input,
-        assume_height_reached: bool,
-        create_height: Option<u32>,
-        current_height: Option<u32>,
-    ) -> Self {
-        PSBTSatisfier {
-            input,
-            assume_height_reached,
-            create_height,
-            current_height,
-        }
-    }
+pub trait PSBTUtils {
+    fn get_utxo_for(&self, input_index: usize) -> Option<TxOut>;
 }
 
-impl<'a> PSBTSatisfier<'a> {
-    fn parse_sig(rawsig: &Vec<u8>) -> Option<BitcoinSig> {
-        let (flag, sig) = rawsig.split_last().unwrap();
-        let flag = bitcoin::SigHashType::from_u32(*flag as u32);
-        let sig = match secp256k1::Signature::from_der(sig) {
-            Ok(sig) => sig,
-            Err(..) => return None,
-        };
-        Some((sig, flag))
-    }
-}
-
-// TODO: also support hash preimages through the "unknown" section of PSBT
-impl<'a> Satisfier<bitcoin::PublicKey> for PSBTSatisfier<'a> {
-    // from https://docs.rs/miniscript/0.12.0/src/miniscript/psbt/mod.rs.html#96
-    fn lookup_sig(&self, pk: &bitcoin::PublicKey) -> Option<BitcoinSig> {
-        debug!("lookup_sig: {}", pk);
-
-        if let Some(rawsig) = self.input.partial_sigs.get(pk) {
-            Self::parse_sig(&rawsig)
-        } else {
-            None
-        }
-    }
-
-    fn lookup_pkh_pk(&self, hash: &hash160::Hash) -> Option<bitcoin::PublicKey> {
-        debug!("lookup_pkh_pk: {}", hash);
+impl PSBTUtils for PSBT {
+    fn get_utxo_for(&self, input_index: usize) -> Option<TxOut> {
+        let tx = &self.global.unsigned_tx;
 
-        for (pk, _) in &self.input.partial_sigs {
-            if &pk.to_pubkeyhash() == hash {
-                return Some(*pk);
-            }
+        if input_index >= tx.input.len() {
+            return None;
         }
 
-        None
-    }
-
-    fn lookup_pkh_sig(&self, hash: &hash160::Hash) -> Option<(bitcoin::PublicKey, BitcoinSig)> {
-        debug!("lookup_pkh_sig: {}", hash);
-
-        for (pk, sig) in &self.input.partial_sigs {
-            if &pk.to_pubkeyhash() == hash {
-                return match Self::parse_sig(&sig) {
-                    Some(bitcoinsig) => Some((*pk, bitcoinsig)),
-                    None => None,
-                };
+        if let Some(input) = self.inputs.get(input_index) {
+            if let Some(wit_utxo) = &input.witness_utxo {
+                Some(wit_utxo.clone())
+            } else if let Some(in_tx) = &input.non_witness_utxo {
+                Some(in_tx.output[tx.input[input_index].previous_output.vout as usize].clone())
+            } else {
+                None
             }
-        }
-
-        None
-    }
-
-    fn check_older(&self, height: u32) -> bool {
-        // TODO: also check if `nSequence` right
-        debug!("check_older: {}", height);
-
-        if let Some(current_height) = self.current_height {
-            // TODO: test >= / >
-            current_height as u64 >= self.create_height.unwrap_or(0) as u64 + height as u64
         } else {
-            self.assume_height_reached
-        }
-    }
-
-    fn check_after(&self, height: u32) -> bool {
-        // TODO: also check if `nLockTime` is right
-        debug!("check_after: {}", height);
-
-        if let Some(current_height) = self.current_height {
-            current_height > height
-        } else {
-            self.assume_height_reached
-        }
-    }
-}
-
-#[derive(Debug)]
-pub struct PSBTSigner<'a> {
-    tx: &'a Transaction,
-    secp: Secp256k1<All>,
-
-    // psbt: &'b psbt::PartiallySignedTransaction,
-    extended_keys: BTreeMap<Fingerprint, ExtendedPrivKey>,
-    private_keys: BTreeMap<PublicKey, PrivateKey>,
-}
-
-impl<'a> PSBTSigner<'a> {
-    pub fn from_descriptor(tx: &'a Transaction, desc: &ExtendedDescriptor) -> Result<Self, Error> {
-        let secp = Secp256k1::gen_new();
-
-        let mut extended_keys = BTreeMap::new();
-        for xprv in desc.get_xprv() {
-            let fing = xprv.fingerprint(&secp);
-            extended_keys.insert(fing, xprv);
-        }
-
-        let mut private_keys = BTreeMap::new();
-        for privkey in desc.get_secret_keys() {
-            let pubkey = privkey.public_key(&secp);
-            private_keys.insert(pubkey, privkey);
-        }
-
-        Ok(PSBTSigner {
-            tx,
-            secp,
-            extended_keys,
-            private_keys,
-        })
-    }
-
-    pub fn extend(&mut self, mut other: PSBTSigner) -> Result<(), Error> {
-        if self.tx.txid() != other.tx.txid() {
-            return Err(Error::DifferentTransactions);
+            None
         }
-
-        self.extended_keys.append(&mut other.extended_keys);
-        self.private_keys.append(&mut other.private_keys);
-
-        Ok(())
-    }
-
-    // TODO: temporary
-    pub fn all_public_keys(&self) -> impl IntoIterator<Item = &PublicKey> {
-        self.private_keys.keys()
-    }
-}
-
-impl<'a> Signer for PSBTSigner<'a> {
-    fn sig_legacy_from_fingerprint(
-        &self,
-        index: usize,
-        sighash: SigHashType,
-        fingerprint: &Fingerprint,
-        path: &DerivationPath,
-        script: &Script,
-    ) -> Result<Option<BitcoinSig>, Error> {
-        self.extended_keys
-            .get(fingerprint)
-            .map_or(Ok(None), |xprv| {
-                let privkey = xprv.derive_priv(&self.secp, path)?;
-                // let derived_pubkey = secp256k1::PublicKey::from_secret_key(&self.secp, &privkey.private_key.key);
-
-                let hash = self.tx.signature_hash(index, script, sighash.as_u32());
-
-                let signature = self.secp.sign(
-                    &Message::from_slice(&hash.into_inner()[..])?,
-                    &privkey.private_key.key,
-                );
-
-                Ok(Some((signature, sighash)))
-            })
-    }
-
-    fn sig_legacy_from_pubkey(
-        &self,
-        index: usize,
-        sighash: SigHashType,
-        public_key: &PublicKey,
-        script: &Script,
-    ) -> Result<Option<BitcoinSig>, Error> {
-        self.private_keys
-            .get(public_key)
-            .map_or(Ok(None), |privkey| {
-                let hash = self.tx.signature_hash(index, script, sighash.as_u32());
-
-                let signature = self
-                    .secp
-                    .sign(&Message::from_slice(&hash.into_inner()[..])?, &privkey.key);
-
-                Ok(Some((signature, sighash)))
-            })
-    }
-
-    fn sig_segwit_from_fingerprint(
-        &self,
-        index: usize,
-        sighash: SigHashType,
-        fingerprint: &Fingerprint,
-        path: &DerivationPath,
-        script: &Script,
-        value: u64,
-    ) -> Result<Option<BitcoinSig>, Error> {
-        self.extended_keys
-            .get(fingerprint)
-            .map_or(Ok(None), |xprv| {
-                let privkey = xprv.derive_priv(&self.secp, path)?;
-
-                let hash = SighashComponents::new(self.tx).sighash_all(
-                    &self.tx.input[index],
-                    script,
-                    value,
-                );
-
-                let signature = self.secp.sign(
-                    &Message::from_slice(&hash.into_inner()[..])?,
-                    &privkey.private_key.key,
-                );
-
-                Ok(Some((signature, sighash)))
-            })
-    }
-
-    fn sig_segwit_from_pubkey(
-        &self,
-        index: usize,
-        sighash: SigHashType,
-        public_key: &PublicKey,
-        script: &Script,
-        value: u64,
-    ) -> Result<Option<BitcoinSig>, Error> {
-        self.private_keys
-            .get(public_key)
-            .map_or(Ok(None), |privkey| {
-                let hash = SighashComponents::new(self.tx).sighash_all(
-                    &self.tx.input[index],
-                    script,
-                    value,
-                );
-
-                let signature = self
-                    .secp
-                    .sign(&Message::from_slice(&hash.into_inner()[..])?, &privkey.key);
-
-                Ok(Some((signature, sighash)))
-            })
     }
 }
diff --git a/src/psbt/utils.rs b/src/psbt/utils.rs
deleted file mode 100644 (file)
index 35552c2..0000000
+++ /dev/null
@@ -1,28 +0,0 @@
-use bitcoin::util::psbt::PartiallySignedTransaction as PSBT;
-use bitcoin::TxOut;
-
-pub trait PSBTUtils {
-    fn get_utxo_for(&self, input_index: usize) -> Option<TxOut>;
-}
-
-impl PSBTUtils for PSBT {
-    fn get_utxo_for(&self, input_index: usize) -> Option<TxOut> {
-        let tx = &self.global.unsigned_tx;
-
-        if input_index >= tx.input.len() {
-            return None;
-        }
-
-        if let Some(input) = self.inputs.get(input_index) {
-            if let Some(wit_utxo) = &input.witness_utxo {
-                Some(wit_utxo.clone())
-            } else if let Some(in_tx) = &input.non_witness_utxo {
-                Some(in_tx.output[tx.input[input_index].previous_output.vout as usize].clone())
-            } else {
-                None
-            }
-        } else {
-            None
-        }
-    }
-}
diff --git a/src/signer.rs b/src/signer.rs
deleted file mode 100644 (file)
index 8f6f8b6..0000000
+++ /dev/null
@@ -1,87 +0,0 @@
-use bitcoin::util::bip32::{DerivationPath, Fingerprint};
-use bitcoin::{PublicKey, Script, SigHashType};
-
-use miniscript::miniscript::satisfy::BitcoinSig;
-
-use crate::error::Error;
-
-pub trait Signer {
-    fn sig_legacy_from_fingerprint(
-        &self,
-        index: usize,
-        sighash: SigHashType,
-        fingerprint: &Fingerprint,
-        path: &DerivationPath,
-        script: &Script,
-    ) -> Result<Option<BitcoinSig>, Error>;
-    fn sig_legacy_from_pubkey(
-        &self,
-        index: usize,
-        sighash: SigHashType,
-        public_key: &PublicKey,
-        script: &Script,
-    ) -> Result<Option<BitcoinSig>, Error>;
-
-    fn sig_segwit_from_fingerprint(
-        &self,
-        index: usize,
-        sighash: SigHashType,
-        fingerprint: &Fingerprint,
-        path: &DerivationPath,
-        script: &Script,
-        value: u64,
-    ) -> Result<Option<BitcoinSig>, Error>;
-    fn sig_segwit_from_pubkey(
-        &self,
-        index: usize,
-        sighash: SigHashType,
-        public_key: &PublicKey,
-        script: &Script,
-        value: u64,
-    ) -> Result<Option<BitcoinSig>, Error>;
-}
-
-#[allow(dead_code)]
-impl dyn Signer {
-    fn sig_legacy_from_fingerprint(
-        &self,
-        _index: usize,
-        _sighash: SigHashType,
-        _fingerprint: &Fingerprint,
-        _path: &DerivationPath,
-        _script: &Script,
-    ) -> Result<Option<BitcoinSig>, Error> {
-        Ok(None)
-    }
-    fn sig_legacy_from_pubkey(
-        &self,
-        _index: usize,
-        _sighash: SigHashType,
-        _public_key: &PublicKey,
-        _script: &Script,
-    ) -> Result<Option<BitcoinSig>, Error> {
-        Ok(None)
-    }
-
-    fn sig_segwit_from_fingerprint(
-        &self,
-        _index: usize,
-        _sighash: SigHashType,
-        _fingerprint: &Fingerprint,
-        _path: &DerivationPath,
-        _script: &Script,
-        _value: u64,
-    ) -> Result<Option<BitcoinSig>, Error> {
-        Ok(None)
-    }
-    fn sig_segwit_from_pubkey(
-        &self,
-        _index: usize,
-        _sighash: SigHashType,
-        _public_key: &PublicKey,
-        _script: &Script,
-        _value: u64,
-    ) -> Result<Option<BitcoinSig>, Error> {
-        Ok(None)
-    }
-}
index 67347990c5d4d172a4e343c5a4ce7a3f5318f042..3a510bf93814481301d958f87b2caf03536d6c1e 100644 (file)
@@ -35,7 +35,9 @@ impl WalletExport {
         label: &str,
         include_blockheight: bool,
     ) -> Result<Self, &'static str> {
-        let descriptor = wallet.descriptor.as_ref().to_string();
+        let descriptor = wallet
+            .descriptor
+            .to_string_with_secret(&wallet.signers.as_key_map());
         Self::is_compatible_with_core(&descriptor)?;
 
         let blockheight = match wallet.database.borrow().iter_txs(false) {
@@ -62,7 +64,7 @@ impl WalletExport {
             != wallet
                 .change_descriptor
                 .as_ref()
-                .map(|d| d.as_ref().to_string())
+                .map(|d| d.to_string_with_secret(&wallet.change_signers.as_key_map()))
         {
             return Err("Incompatible change descriptor");
         }
@@ -193,14 +195,14 @@ mod test {
     #[test]
     fn test_export_multi() {
         let descriptor = "wsh(multi(2,\
-                                [73756c7f/48h/0h/0h/2h]tpubDCKxNyM3bLgbEX13Mcd8mYxbVg9ajDkWXMh29hMWBurKfVmBfWAM96QVP3zaUcN51HvkZ3ar4VwP82kC8JZhhux8vFQoJintSpVBwpFvyU3/0/*,\
-                                [f9f62194/48h/0h/0h/2h]tpubDDp3ZSH1yCwusRppH7zgSxq2t1VEUyXSeEp8E5aFS8m43MknUjiF1bSLo3CGWAxbDyhF1XowA5ukPzyJZjznYk3kYi6oe7QxtX2euvKWsk4/0/*,\
-                                [c98b1535/48h/0h/0h/2h]tpubDCDi5W4sP6zSnzJeowy8rQDVhBdRARaPhK1axABi8V1661wEPeanpEXj4ZLAUEoikVtoWcyK26TKKJSecSfeKxwHCcRrge9k1ybuiL71z4a/0/*\
+                                [73756c7f/48'/0'/0'/2']tpubDCKxNyM3bLgbEX13Mcd8mYxbVg9ajDkWXMh29hMWBurKfVmBfWAM96QVP3zaUcN51HvkZ3ar4VwP82kC8JZhhux8vFQoJintSpVBwpFvyU3/0/*,\
+                                [f9f62194/48'/0'/0'/2']tpubDDp3ZSH1yCwusRppH7zgSxq2t1VEUyXSeEp8E5aFS8m43MknUjiF1bSLo3CGWAxbDyhF1XowA5ukPzyJZjznYk3kYi6oe7QxtX2euvKWsk4/0/*,\
+                                [c98b1535/48'/0'/0'/2']tpubDCDi5W4sP6zSnzJeowy8rQDVhBdRARaPhK1axABi8V1661wEPeanpEXj4ZLAUEoikVtoWcyK26TKKJSecSfeKxwHCcRrge9k1ybuiL71z4a/0/*\
                           ))";
         let change_descriptor = "wsh(multi(2,\
-                                       [73756c7f/48h/0h/0h/2h]tpubDCKxNyM3bLgbEX13Mcd8mYxbVg9ajDkWXMh29hMWBurKfVmBfWAM96QVP3zaUcN51HvkZ3ar4VwP82kC8JZhhux8vFQoJintSpVBwpFvyU3/1/*,\
-                                       [f9f62194/48h/0h/0h/2h]tpubDDp3ZSH1yCwusRppH7zgSxq2t1VEUyXSeEp8E5aFS8m43MknUjiF1bSLo3CGWAxbDyhF1XowA5ukPzyJZjznYk3kYi6oe7QxtX2euvKWsk4/1/*,\
-                                       [c98b1535/48h/0h/0h/2h]tpubDCDi5W4sP6zSnzJeowy8rQDVhBdRARaPhK1axABi8V1661wEPeanpEXj4ZLAUEoikVtoWcyK26TKKJSecSfeKxwHCcRrge9k1ybuiL71z4a/1/*\
+                                       [73756c7f/48'/0'/0'/2']tpubDCKxNyM3bLgbEX13Mcd8mYxbVg9ajDkWXMh29hMWBurKfVmBfWAM96QVP3zaUcN51HvkZ3ar4VwP82kC8JZhhux8vFQoJintSpVBwpFvyU3/1/*,\
+                                       [f9f62194/48'/0'/0'/2']tpubDDp3ZSH1yCwusRppH7zgSxq2t1VEUyXSeEp8E5aFS8m43MknUjiF1bSLo3CGWAxbDyhF1XowA5ukPzyJZjznYk3kYi6oe7QxtX2euvKWsk4/1/*,\
+                                       [c98b1535/48'/0'/0'/2']tpubDCDi5W4sP6zSnzJeowy8rQDVhBdRARaPhK1axABi8V1661wEPeanpEXj4ZLAUEoikVtoWcyK26TKKJSecSfeKxwHCcRrge9k1ybuiL71z4a/1/*\
                                  ))";
 
         let wallet: OfflineWallet<_> = Wallet::new_offline(
index 24c6e1fc819cb3fafc02ba77e09b74a19f3e4a0f..0ea88eb1ec6400754073f50df1320de3016f98a3 100644 (file)
@@ -2,17 +2,14 @@ use std::cell::RefCell;
 use std::collections::HashMap;
 use std::collections::{BTreeMap, HashSet};
 use std::ops::{Deref, DerefMut};
-use std::str::FromStr;
+use std::sync::Arc;
 
-use bitcoin::blockdata::opcodes;
-use bitcoin::blockdata::script::Builder;
 use bitcoin::consensus::encode::serialize;
+use bitcoin::util::bip32::ChildNumber;
 use bitcoin::util::psbt::PartiallySignedTransaction as PSBT;
-use bitcoin::{
-    Address, Network, OutPoint, PublicKey, Script, SigHashType, Transaction, TxOut, Txid,
-};
+use bitcoin::{Address, Network, OutPoint, Script, SigHashType, Transaction, TxOut, Txid};
 
-use miniscript::BitcoinSig;
+use miniscript::descriptor::DescriptorPublicKey;
 
 #[allow(unused_imports)]
 use log::{debug, error, info, trace};
@@ -20,19 +17,23 @@ use log::{debug, error, info, trace};
 pub mod coin_selection;
 pub mod export;
 mod rbf;
+pub mod signer;
 pub mod time;
 pub mod tx_builder;
 pub mod utils;
 
+use signer::{Signer, SignersContainer};
 use tx_builder::TxBuilder;
-use utils::{FeeRate, IsDust};
+use utils::{After, FeeRate, IsDust, Older};
 
 use crate::blockchain::{Blockchain, OfflineBlockchain, OnlineBlockchain, Progress};
 use crate::database::{BatchDatabase, BatchOperations, DatabaseUtils};
-use crate::descriptor::{get_checksum, DescriptorMeta, ExtendedDescriptor, ExtractPolicy, Policy};
+use crate::descriptor::{
+    get_checksum, DescriptorMeta, DescriptorScripts, ExtendedDescriptor, ExtractPolicy, Policy,
+};
 use crate::error::Error;
-use crate::psbt::{utils::PSBTUtils, PSBTSatisfier, PSBTSigner};
-use crate::signer::Signer;
+use crate::psbt::PSBTUtils;
+// use crate::psbt::{utils::PSBTUtils, PSBTSatisfier, PSBTSigner};
 use crate::types::*;
 
 const CACHE_ADDR_BATCH_SIZE: u32 = 100;
@@ -42,6 +43,10 @@ pub type OfflineWallet<D> = Wallet<OfflineBlockchain, D>;
 pub struct Wallet<B: Blockchain, D: BatchDatabase> {
     descriptor: ExtendedDescriptor,
     change_descriptor: Option<ExtendedDescriptor>,
+
+    signers: Arc<SignersContainer<DescriptorPublicKey>>,
+    change_signers: Arc<SignersContainer<DescriptorPublicKey>>,
+
     network: Network,
 
     current_height: Option<u32>,
@@ -66,27 +71,32 @@ where
             ScriptType::External,
             get_checksum(descriptor)?.as_bytes(),
         )?;
-        let descriptor = ExtendedDescriptor::from_str(descriptor)?;
-        let change_descriptor = match change_descriptor {
+        let (descriptor, keymap) = ExtendedDescriptor::parse_secret(descriptor)?;
+        let signers = Arc::new(SignersContainer::from(keymap));
+        let (change_descriptor, change_signers) = match change_descriptor {
             Some(desc) => {
                 database.check_descriptor_checksum(
                     ScriptType::Internal,
                     get_checksum(desc)?.as_bytes(),
                 )?;
 
-                let parsed = ExtendedDescriptor::from_str(desc)?;
-                if !parsed.same_structure(descriptor.as_ref()) {
-                    return Err(Error::DifferentDescriptorStructure);
-                }
+                let (change_descriptor, change_keymap) = ExtendedDescriptor::parse_secret(desc)?;
+                let change_signers = Arc::new(SignersContainer::from(change_keymap));
+                // if !parsed.same_structure(descriptor.as_ref()) {
+                //     return Err(Error::DifferentDescriptorStructure);
+                // }
 
-                Some(parsed)
+                (Some(change_descriptor), change_signers)
             }
-            None => None,
+            None => (None, Arc::new(SignersContainer::new())),
         };
 
         Ok(Wallet {
             descriptor,
             change_descriptor,
+            signers,
+            change_signers,
+
             network,
 
             current_height: None,
@@ -100,7 +110,7 @@ where
         let index = self.fetch_and_increment_index(ScriptType::External)?;
 
         self.descriptor
-            .derive(index)?
+            .derive(&[ChildNumber::from_normal_idx(index).unwrap()])
             .address(self.network)
             .ok_or(Error::ScriptDoesntHaveAddressForm)
     }
@@ -133,7 +143,10 @@ where
         }
 
         // TODO: fetch both internal and external policies
-        let policy = self.descriptor.extract_policy()?.unwrap();
+        let policy = self
+            .descriptor
+            .extract_policy(Arc::clone(&self.signers))?
+            .unwrap();
         if policy.requires_path() && builder.policy_path.is_none() {
             return Err(Error::SpendingPolicyRequired);
         }
@@ -220,10 +233,10 @@ where
         // TODO: use the right weight instead of the maximum, and only fall-back to it if the
         // script is unknown in the database
         let input_witness_weight = std::cmp::max(
-            self.get_descriptor_for(ScriptType::Internal)
+            self.get_descriptor_for_script_type(ScriptType::Internal)
                 .0
                 .max_satisfaction_weight(),
-            self.get_descriptor_for(ScriptType::External)
+            self.get_descriptor_for_script_type(ScriptType::External)
                 .0
                 .max_satisfaction_weight(),
         );
@@ -369,7 +382,7 @@ where
                 // `get_deget_descriptor_for` to find what's the ScriptType for `Internal`
                 // addresses really is, because if there's no change_descriptor it's actually equal
                 // to "External"
-                let (_, change_type) = self.get_descriptor_for(ScriptType::Internal);
+                let (_, change_type) = self.get_descriptor_for_script_type(ScriptType::Internal);
                 match self
                     .database
                     .borrow()
@@ -435,10 +448,10 @@ where
                     // TODO: use the right weight instead of the maximum, and only fall-back to it if the
                     // script is unknown in the database
                     let input_witness_weight = std::cmp::max(
-                        self.get_descriptor_for(ScriptType::Internal)
+                        self.get_descriptor_for_script_type(ScriptType::Internal)
                             .0
                             .max_satisfaction_weight(),
-                        self.get_descriptor_for(ScriptType::External)
+                        self.get_descriptor_for_script_type(ScriptType::External)
                             .0
                             .max_satisfaction_weight(),
                     );
@@ -545,139 +558,11 @@ where
         // this helps us doing our job later
         self.add_input_hd_keypaths(&mut psbt)?;
 
-        let tx = &psbt.global.unsigned_tx;
-
-        let mut signer = PSBTSigner::from_descriptor(&psbt.global.unsigned_tx, &self.descriptor)?;
-        if let Some(desc) = &self.change_descriptor {
-            let change_signer = PSBTSigner::from_descriptor(&psbt.global.unsigned_tx, desc)?;
-            signer.extend(change_signer)?;
-        }
-
-        // sign everything we can. TODO: ideally we should only sign with the keys in the policy
-        // path selected, if present
-        for (i, input) in psbt.inputs.iter_mut().enumerate() {
-            let sighash = input.sighash_type.unwrap_or(SigHashType::All);
-            let prevout = tx.input[i].previous_output;
-
-            let mut partial_sigs = BTreeMap::new();
-            {
-                let mut push_sig = |pubkey: &PublicKey, opt_sig: Option<BitcoinSig>| {
-                    if let Some((signature, sighash)) = opt_sig {
-                        let mut concat_sig = Vec::new();
-                        concat_sig.extend_from_slice(&signature.serialize_der());
-                        concat_sig.extend_from_slice(&[sighash as u8]);
-                        //input.partial_sigs.insert(*pubkey, concat_sig);
-                        partial_sigs.insert(*pubkey, concat_sig);
-                    }
-                };
-
-                if let Some(non_wit_utxo) = &input.non_witness_utxo {
-                    if non_wit_utxo.txid() != prevout.txid {
-                        return Err(Error::InputTxidMismatch((non_wit_utxo.txid(), prevout)));
-                    }
-
-                    let prev_script = &non_wit_utxo.output
-                        [psbt.global.unsigned_tx.input[i].previous_output.vout as usize]
-                        .script_pubkey;
-
-                    // return (signature, sighash) from here
-                    let sign_script = if let Some(redeem_script) = &input.redeem_script {
-                        if &redeem_script.to_p2sh() != prev_script {
-                            return Err(Error::InputRedeemScriptMismatch((
-                                prev_script.clone(),
-                                redeem_script.clone(),
-                            )));
-                        }
-
-                        redeem_script
-                    } else {
-                        prev_script
-                    };
-
-                    for (pubkey, (fing, path)) in &input.hd_keypaths {
-                        push_sig(
-                            pubkey,
-                            signer.sig_legacy_from_fingerprint(
-                                i,
-                                sighash,
-                                fing,
-                                path,
-                                sign_script,
-                            )?,
-                        );
-                    }
-                    // TODO: this sucks, we sign with every key
-                    for pubkey in signer.all_public_keys() {
-                        push_sig(
-                            pubkey,
-                            signer.sig_legacy_from_pubkey(i, sighash, pubkey, sign_script)?,
-                        );
-                    }
-                } else if let Some(witness_utxo) = &input.witness_utxo {
-                    let value = witness_utxo.value;
-
-                    let script = match &input.redeem_script {
-                        Some(script) if script.to_p2sh() != witness_utxo.script_pubkey => {
-                            return Err(Error::InputRedeemScriptMismatch((
-                                witness_utxo.script_pubkey.clone(),
-                                script.clone(),
-                            )))
-                        }
-                        Some(script) => script,
-                        None => &witness_utxo.script_pubkey,
-                    };
-
-                    let sign_script = if script.is_v0_p2wpkh() {
-                        self.to_p2pkh(&script.as_bytes()[2..])
-                    } else if script.is_v0_p2wsh() {
-                        match &input.witness_script {
-                            None => Err(Error::InputMissingWitnessScript(i)),
-                            Some(witness_script) if script != &witness_script.to_v0_p2wsh() => {
-                                Err(Error::InputRedeemScriptMismatch((
-                                    script.clone(),
-                                    witness_script.clone(),
-                                )))
-                            }
-                            Some(witness_script) => Ok(witness_script),
-                        }?
-                        .clone()
-                    } else {
-                        return Err(Error::InputUnknownSegwitScript(script.clone()));
-                    };
-
-                    for (pubkey, (fing, path)) in &input.hd_keypaths {
-                        push_sig(
-                            pubkey,
-                            signer.sig_segwit_from_fingerprint(
-                                i,
-                                sighash,
-                                fing,
-                                path,
-                                &sign_script,
-                                value,
-                            )?,
-                        );
-                    }
-                    // TODO: this sucks, we sign with every key
-                    for pubkey in signer.all_public_keys() {
-                        push_sig(
-                            pubkey,
-                            signer.sig_segwit_from_pubkey(
-                                i,
-                                sighash,
-                                pubkey,
-                                &sign_script,
-                                value,
-                            )?,
-                        );
-                    }
-                } else {
-                    return Err(Error::MissingUTXO);
-                }
+        for index in 0..psbt.inputs.len() {
+            self.signers.sign(&mut psbt, index)?;
+            if self.change_descriptor.is_some() {
+                self.change_signers.sign(&mut psbt, index)?;
             }
-
-            // push all the signatures into the psbt
-            input.partial_sigs.append(&mut partial_sigs);
         }
 
         // attempt to finalize
@@ -688,9 +573,13 @@ where
 
     pub fn policies(&self, script_type: ScriptType) -> Result<Option<Policy>, Error> {
         match (script_type, self.change_descriptor.as_ref()) {
-            (ScriptType::External, _) => Ok(self.descriptor.extract_policy()?),
+            (ScriptType::External, _) => {
+                Ok(self.descriptor.extract_policy(Arc::clone(&self.signers))?)
+            }
             (ScriptType::Internal, None) => Ok(None),
-            (ScriptType::Internal, Some(desc)) => Ok(desc.extract_policy()?),
+            (ScriptType::Internal, Some(desc)) => {
+                Ok(desc.extract_policy(Arc::clone(&self.change_signers))?)
+            }
         }
     }
 
@@ -699,9 +588,9 @@ where
         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::External, _) => Ok(Some(self.descriptor.clone())),
             (ScriptType::Internal, None) => Ok(None),
-            (ScriptType::Internal, Some(desc)) => Ok(Some(desc.as_public_version()?)),
+            (ScriptType::Internal, Some(desc)) => Ok(Some(desc.clone())),
         }
     }
 
@@ -712,18 +601,7 @@ where
     ) -> Result<bool, Error> {
         let mut tx = psbt.global.unsigned_tx.clone();
 
-        for (n, input) in tx.input.iter_mut().enumerate() {
-            // safe to run only on the descriptor because we assume the change descriptor also has
-            // the same structure
-            let desc = self.descriptor.derive_from_psbt_input(psbt, n);
-            debug!("{:?}", psbt.inputs[n].hd_keypaths);
-            debug!("reconstructed descriptor is {:?}", desc);
-
-            let desc = match desc {
-                Err(_) => return Ok(false),
-                Ok(desc) => desc,
-            };
-
+        for (n, (input, psbt_input)) in tx.input.iter_mut().zip(psbt.inputs.iter()).enumerate() {
             // if the height is None in the database it means it's still unconfirmed, so consider
             // that as a very high value
             let create_height = self
@@ -738,10 +616,43 @@ where
                 n, input.previous_output, create_height, current_height
             );
 
-            let satisfier =
-                PSBTSatisfier::new(&psbt.inputs[n], false, create_height, current_height);
+            // - Try to derive the descriptor by looking at the txout. If it's in our database, we
+            //   know exactly which `script_type` to use, and which derivation index it is
+            // - If that fails, try to derive it by looking at the psbt input: the complete logic
+            //   is in `src/descriptor/mod.rs`, but it will basically look at `hd_keypaths`,
+            //   `redeem_script` and `witness_script` to determine the right derivation
+            // - If that also fails, it will try it on the internal descriptor, if present
+            let desc = if let Some(desc) = psbt
+                .get_utxo_for(n)
+                .map(|txout| self.get_descriptor_for_txout(&txout))
+                .transpose()?
+                .flatten()
+            {
+                desc
+            } else if let Some(desc) = self
+                .descriptor
+                .derive_from_psbt_input(psbt_input, psbt.get_utxo_for(n))
+            {
+                desc
+            } else if let Some(desc) = self
+                .change_descriptor
+                .as_ref()
+                .and_then(|desc| desc.derive_from_psbt_input(psbt_input, psbt.get_utxo_for(n)))
+            {
+                desc
+            } else {
+                debug!("Couldn't find the right derived descriptor for input {}", n);
+                return Ok(false);
+            };
 
-            match desc.satisfy(input, satisfier) {
+            match desc.satisfy(
+                input,
+                (
+                    psbt_input.clone(),
+                    After::new(current_height, false),
+                    Older::new(current_height, create_height, false),
+                ),
+            ) {
                 Ok(_) => continue,
                 Err(e) => {
                     debug!("satisfy error {:?} for input {}", e, n);
@@ -761,7 +672,10 @@ where
 
     // Internals
 
-    fn get_descriptor_for(&self, script_type: ScriptType) -> (&ExtendedDescriptor, ScriptType) {
+    fn get_descriptor_for_script_type(
+        &self,
+        script_type: ScriptType,
+    ) -> (&ExtendedDescriptor, ScriptType) {
         let desc = match script_type {
             ScriptType::Internal if self.change_descriptor.is_some() => (
                 self.change_descriptor.as_ref().unwrap(),
@@ -773,25 +687,26 @@ where
         desc
     }
 
-    fn to_p2pkh(&self, pubkey_hash: &[u8]) -> Script {
-        Builder::new()
-            .push_opcode(opcodes::all::OP_DUP)
-            .push_opcode(opcodes::all::OP_HASH160)
-            .push_slice(pubkey_hash)
-            .push_opcode(opcodes::all::OP_EQUALVERIFY)
-            .push_opcode(opcodes::all::OP_CHECKSIG)
-            .into_script()
+    fn get_descriptor_for_txout(&self, txout: &TxOut) -> Result<Option<ExtendedDescriptor>, Error> {
+        Ok(self
+            .database
+            .borrow()
+            .get_path_from_script_pubkey(&txout.script_pubkey)?
+            .map(|(script_type, child)| (self.get_descriptor_for_script_type(script_type).0, child))
+            .map(|(desc, child)| desc.derive(&[ChildNumber::from_normal_idx(child).unwrap()])))
     }
 
     fn get_change_address(&self) -> Result<Script, Error> {
-        let (desc, script_type) = self.get_descriptor_for(ScriptType::Internal);
+        let (desc, script_type) = self.get_descriptor_for_script_type(ScriptType::Internal);
         let index = self.fetch_and_increment_index(script_type)?;
 
-        Ok(desc.derive(index)?.script_pubkey())
+        Ok(desc
+            .derive(&[ChildNumber::from_normal_idx(index).unwrap()])
+            .script_pubkey())
     }
 
     fn fetch_and_increment_index(&self, script_type: ScriptType) -> Result<u32, Error> {
-        let (descriptor, script_type) = self.get_descriptor_for(script_type);
+        let (descriptor, script_type) = self.get_descriptor_for_script_type(script_type);
         let index = match descriptor.is_fixed() {
             true => 0,
             false => self
@@ -818,7 +733,7 @@ where
         from: u32,
         mut count: u32,
     ) -> Result<(), Error> {
-        let (descriptor, script_type) = self.get_descriptor_for(script_type);
+        let (descriptor, script_type) = self.get_descriptor_for_script_type(script_type);
         if descriptor.is_fixed() {
             if from > 0 {
                 return Ok(());
@@ -832,7 +747,9 @@ where
         let start_time = time::Instant::new();
         for i in from..(from + count) {
             address_batch.set_script_pubkey(
-                &descriptor.derive(i)?.script_pubkey(),
+                &descriptor
+                    .derive(&[ChildNumber::from_normal_idx(i).unwrap()])
+                    .script_pubkey(),
                 script_type,
                 i,
             )?;
@@ -924,9 +841,9 @@ where
                 None => continue,
             };
 
-            let (desc, _) = self.get_descriptor_for(script_type);
+            let (desc, _) = self.get_descriptor_for_script_type(script_type);
             psbt_input.hd_keypaths = desc.get_hd_keypaths(child)?;
-            let derived_descriptor = desc.derive(child)?;
+            let derived_descriptor = desc.derive(&[ChildNumber::from_normal_idx(child).unwrap()]);
 
             psbt_input.redeem_script = derived_descriptor.psbt_redeem_script();
             psbt_input.witness_script = derived_descriptor.psbt_witness_script();
@@ -957,7 +874,7 @@ where
                 .borrow()
                 .get_path_from_script_pubkey(&tx_output.script_pubkey)?
             {
-                let (desc, _) = self.get_descriptor_for(script_type);
+                let (desc, _) = self.get_descriptor_for_script_type(script_type);
                 psbt_output.hd_keypaths = desc.get_hd_keypaths(child)?;
             }
         }
@@ -982,7 +899,7 @@ where
                     debug!("Found descriptor {:?}/{}", script_type, child);
 
                     // merge hd_keypaths
-                    let (desc, _) = self.get_descriptor_for(script_type);
+                    let (desc, _) = self.get_descriptor_for_script_type(script_type);
                     let mut hd_keypaths = desc.get_hd_keypaths(child)?;
                     psbt_input.hd_keypaths.append(&mut hd_keypaths);
                 }
@@ -1086,13 +1003,12 @@ where
 
 #[cfg(test)]
 mod test {
-    use bitcoin::Network;
+    use std::str::FromStr;
 
-    use miniscript::Descriptor;
+    use bitcoin::Network;
 
     use crate::database::memory::MemoryDatabase;
     use crate::database::Database;
-    use crate::descriptor::ExtendedDescriptor;
     use crate::types::ScriptType;
 
     use super::*;
@@ -1205,12 +1121,12 @@ mod test {
         descriptor: &str,
     ) -> (
         OfflineWallet<MemoryDatabase>,
-        (ExtendedDescriptor, Option<ExtendedDescriptor>),
+        (String, Option<String>),
         bitcoin::Txid,
     ) {
         let descriptors = testutils!(@descriptors (descriptor));
         let wallet: OfflineWallet<_> = Wallet::new_offline(
-            &descriptors.0.to_string(),
+            &descriptors.0,
             None,
             Network::Regtest,
             MemoryDatabase::new(),
diff --git a/src/wallet/signer.rs b/src/wallet/signer.rs
new file mode 100644 (file)
index 0000000..7cca921
--- /dev/null
@@ -0,0 +1,327 @@
+use std::any::Any;
+use std::collections::HashMap;
+use std::fmt;
+
+use bitcoin::blockdata::opcodes;
+use bitcoin::blockdata::script::Builder as ScriptBuilder;
+use bitcoin::hashes::{hash160, Hash};
+use bitcoin::secp256k1::{Message, Secp256k1};
+use bitcoin::util::bip32::{ExtendedPrivKey, Fingerprint};
+use bitcoin::util::{bip143, psbt};
+use bitcoin::{PrivateKey, SigHash, SigHashType};
+
+use miniscript::descriptor::{DescriptorPublicKey, DescriptorSecretKey, DescriptorXKey, KeyMap};
+use miniscript::{Legacy, MiniscriptKey, Segwitv0};
+
+use crate::descriptor::XKeyUtils;
+
+/// Identifier of a signer in the `SignersContainers`. Used as a key to find the right signer among
+/// many of them
+#[derive(Debug, PartialEq, Eq, Hash)]
+pub enum SignerId<Pk: MiniscriptKey> {
+    PkHash(<Pk as MiniscriptKey>::Hash),
+    Fingerprint(Fingerprint),
+}
+
+impl From<hash160::Hash> for SignerId<DescriptorPublicKey> {
+    fn from(hash: hash160::Hash) -> SignerId<DescriptorPublicKey> {
+        SignerId::PkHash(hash)
+    }
+}
+
+impl From<Fingerprint> for SignerId<DescriptorPublicKey> {
+    fn from(fing: Fingerprint) -> SignerId<DescriptorPublicKey> {
+        SignerId::Fingerprint(fing)
+    }
+}
+
+/// Signing error
+#[derive(Debug, PartialEq, Eq, Clone)]
+pub enum SignerError {
+    /// The private key is missing for the required public key
+    MissingKey,
+    /// The user canceled the operation
+    UserCanceled,
+    /// The sighash is missing in the PSBT input
+    MissingSighash,
+    /// Input index is out of range
+    InputIndexOutOfRange,
+    /// The `non_witness_utxo` field of the transaction is required to sign this input
+    MissingNonWitnessUtxo,
+    /// The `non_witness_utxo` specified is invalid
+    InvalidNonWitnessUtxo,
+    /// The `witness_utxo` field of the transaction is required to sign this input
+    MissingWitnessUtxo,
+    /// The `witness_script` field of the transaction is requied to sign this input
+    MissingWitnessScript,
+    /// The fingerprint and derivation path are missing from the psbt input
+    MissingHDKeypath,
+}
+
+/// Trait for signers
+pub trait Signer: fmt::Debug {
+    fn sign(
+        &self,
+        psbt: &mut psbt::PartiallySignedTransaction,
+        input_index: usize,
+    ) -> Result<(), SignerError>;
+
+    fn descriptor_secret_key(&self) -> Option<DescriptorSecretKey> {
+        None
+    }
+}
+
+impl Signer for DescriptorXKey<ExtendedPrivKey> {
+    fn sign(
+        &self,
+        psbt: &mut psbt::PartiallySignedTransaction,
+        input_index: usize,
+    ) -> Result<(), SignerError> {
+        if input_index >= psbt.inputs.len() {
+            return Err(SignerError::InputIndexOutOfRange);
+        }
+
+        let deriv_path = match psbt.inputs[input_index]
+            .hd_keypaths
+            .iter()
+            .filter_map(|(_, &(fingerprint, ref path))| self.matches(fingerprint.clone(), &path))
+            .next()
+        {
+            Some(deriv_path) => deriv_path,
+            None => return Ok(()), // TODO: should report an error maybe?
+        };
+
+        let ctx = Secp256k1::signing_only();
+
+        let derived_key = self.xkey.derive_priv(&ctx, &deriv_path).unwrap();
+        derived_key.private_key.sign(psbt, input_index)
+    }
+
+    fn descriptor_secret_key(&self) -> Option<DescriptorSecretKey> {
+        Some(DescriptorSecretKey::XPrv(self.clone()))
+    }
+}
+
+impl Signer for PrivateKey {
+    fn sign(
+        &self,
+        psbt: &mut psbt::PartiallySignedTransaction,
+        input_index: usize,
+    ) -> Result<(), SignerError> {
+        if input_index >= psbt.inputs.len() {
+            return Err(SignerError::InputIndexOutOfRange);
+        }
+
+        let ctx = Secp256k1::signing_only();
+
+        let pubkey = self.public_key(&ctx);
+        if psbt.inputs[input_index].partial_sigs.contains_key(&pubkey) {
+            return Ok(());
+        }
+
+        // FIXME: use the presence of `witness_utxo` as an indication that we should make a bip143
+        // sig. Does this make sense? Should we add an extra argument to explicitly swith between
+        // these? The original idea was to declare sign() as sign<Ctx: ScriptContex>() and use Ctx,
+        // but that violates the rules for trait-objects, so we can't do it.
+        let (hash, sighash) = match psbt.inputs[input_index].witness_utxo {
+            Some(_) => Segwitv0::sighash(psbt, input_index)?,
+            None => Legacy::sighash(psbt, input_index)?,
+        };
+
+        let signature = ctx.sign(
+            &Message::from_slice(&hash.into_inner()[..]).unwrap(),
+            &self.key,
+        );
+
+        let mut final_signature = Vec::with_capacity(75);
+        final_signature.extend_from_slice(&signature.serialize_der());
+        final_signature.push(sighash.as_u32() as u8);
+
+        psbt.inputs[input_index]
+            .partial_sigs
+            .insert(pubkey, final_signature);
+
+        Ok(())
+    }
+
+    fn descriptor_secret_key(&self) -> Option<DescriptorSecretKey> {
+        Some(DescriptorSecretKey::PrivKey(self.clone()))
+    }
+}
+
+/// Container for multiple signers
+#[derive(Debug, Default)]
+pub struct SignersContainer<Pk: MiniscriptKey>(HashMap<SignerId<Pk>, Box<dyn Signer>>);
+
+impl SignersContainer<DescriptorPublicKey> {
+    pub fn as_key_map(&self) -> KeyMap {
+        self.0
+            .values()
+            .filter_map(|signer| signer.descriptor_secret_key())
+            .filter_map(|secret| secret.as_public().ok().map(|public| (public, secret)))
+            .collect()
+    }
+}
+
+impl<Pk: MiniscriptKey + Any> Signer for SignersContainer<Pk> {
+    fn sign(
+        &self,
+        psbt: &mut psbt::PartiallySignedTransaction,
+        input_index: usize,
+    ) -> Result<(), SignerError> {
+        for signer in self.0.values() {
+            signer.sign(psbt, input_index)?;
+        }
+
+        Ok(())
+    }
+}
+
+impl From<KeyMap> for SignersContainer<DescriptorPublicKey> {
+    fn from(keymap: KeyMap) -> SignersContainer<DescriptorPublicKey> {
+        let mut container = SignersContainer::new();
+
+        for (_, secret) in keymap {
+            match secret {
+                DescriptorSecretKey::PrivKey(private_key) => container.add_external(
+                    SignerId::from(
+                        private_key
+                            .public_key(&Secp256k1::signing_only())
+                            .to_pubkeyhash(),
+                    ),
+                    Box::new(private_key),
+                ),
+                DescriptorSecretKey::XPrv(xprv) => {
+                    container.add_external(SignerId::from(xprv.root_fingerprint()), Box::new(xprv))
+                }
+            };
+        }
+
+        container
+    }
+}
+
+impl<Pk: MiniscriptKey> SignersContainer<Pk> {
+    /// Default constructor
+    pub fn new() -> Self {
+        SignersContainer(HashMap::new())
+    }
+
+    /// Adds an external signer to the container for the specified id. Optionally returns the
+    /// signer that was previosuly in the container, if any
+    pub fn add_external(
+        &mut self,
+        id: SignerId<Pk>,
+        signer: Box<dyn Signer>,
+    ) -> Option<Box<dyn Signer>> {
+        self.0.insert(id, signer)
+    }
+
+    /// Removes a signer from the container and returns it
+    pub fn remove(&mut self, id: SignerId<Pk>) -> Option<Box<dyn Signer>> {
+        self.0.remove(&id)
+    }
+
+    /// Returns the list of identifiers of all the signers in the container
+    pub fn ids(&self) -> Vec<&SignerId<Pk>> {
+        self.0.keys().collect()
+    }
+
+    /// Finds the signer with a given id in the container
+    pub fn find(&self, id: SignerId<Pk>) -> Option<&Box<dyn Signer>> {
+        self.0.get(&id)
+    }
+}
+
+pub trait ComputeSighash {
+    fn sighash(
+        psbt: &psbt::PartiallySignedTransaction,
+        input_index: usize,
+    ) -> Result<(SigHash, SigHashType), SignerError>;
+}
+
+impl ComputeSighash for Legacy {
+    fn sighash(
+        psbt: &psbt::PartiallySignedTransaction,
+        input_index: usize,
+    ) -> Result<(SigHash, SigHashType), SignerError> {
+        if input_index >= psbt.inputs.len() {
+            return Err(SignerError::InputIndexOutOfRange);
+        }
+
+        let psbt_input = &psbt.inputs[input_index];
+        let tx_input = &psbt.global.unsigned_tx.input[input_index];
+
+        let sighash = psbt_input.sighash_type.ok_or(SignerError::MissingSighash)?;
+        let script = match &psbt_input.redeem_script {
+            &Some(ref redeem_script) => redeem_script.clone(),
+            &None => {
+                let non_witness_utxo = psbt_input
+                    .non_witness_utxo
+                    .as_ref()
+                    .ok_or(SignerError::MissingNonWitnessUtxo)?;
+                let prev_out = non_witness_utxo
+                    .output
+                    .get(tx_input.previous_output.vout as usize)
+                    .ok_or(SignerError::InvalidNonWitnessUtxo)?;
+
+                prev_out.script_pubkey.clone()
+            }
+        };
+
+        Ok((
+            psbt.global
+                .unsigned_tx
+                .signature_hash(input_index, &script, sighash.as_u32()),
+            sighash,
+        ))
+    }
+}
+
+impl ComputeSighash for Segwitv0 {
+    fn sighash(
+        psbt: &psbt::PartiallySignedTransaction,
+        input_index: usize,
+    ) -> Result<(SigHash, SigHashType), SignerError> {
+        if input_index >= psbt.inputs.len() {
+            return Err(SignerError::InputIndexOutOfRange);
+        }
+
+        let psbt_input = &psbt.inputs[input_index];
+
+        let sighash = psbt_input.sighash_type.ok_or(SignerError::MissingSighash)?;
+
+        let witness_utxo = psbt_input
+            .witness_utxo
+            .as_ref()
+            .ok_or(SignerError::MissingNonWitnessUtxo)?;
+        let value = witness_utxo.value;
+
+        let script = match &psbt_input.witness_script {
+            &Some(ref witness_script) => witness_script.clone(),
+            &None => {
+                if witness_utxo.script_pubkey.is_v0_p2wpkh() {
+                    ScriptBuilder::new()
+                        .push_opcode(opcodes::all::OP_DUP)
+                        .push_opcode(opcodes::all::OP_HASH160)
+                        .push_slice(&witness_utxo.script_pubkey[2..])
+                        .push_opcode(opcodes::all::OP_EQUALVERIFY)
+                        .push_opcode(opcodes::all::OP_CHECKSIG)
+                        .into_script()
+                } else {
+                    return Err(SignerError::MissingWitnessScript);
+                }
+            }
+        };
+
+        Ok((
+            bip143::SigHashCache::new(&psbt.global.unsigned_tx).signature_hash(
+                input_index,
+                &script,
+                value,
+                sighash,
+            ),
+            sighash,
+        ))
+    }
+}
index f6c287cf052af61f680439a51be94c65047f35c4..a4a5510a0f5ceef9930d40fe4d644c712e6151c5 100644 (file)
@@ -1,3 +1,5 @@
+use miniscript::{MiniscriptKey, Satisfier};
+
 // De-facto standard "dust limit" (even though it should change based on the output type)
 const DUST_LIMIT_SATOSHI: u64 = 546;
 
@@ -42,6 +44,61 @@ impl std::default::Default for FeeRate {
     }
 }
 
+pub struct After {
+    pub current_height: Option<u32>,
+    pub assume_height_reached: bool,
+}
+
+impl After {
+    pub(crate) fn new(current_height: Option<u32>, assume_height_reached: bool) -> After {
+        After {
+            current_height,
+            assume_height_reached,
+        }
+    }
+}
+
+impl<Pk: MiniscriptKey> Satisfier<Pk> for After {
+    fn check_after(&self, n: u32) -> bool {
+        if let Some(current_height) = self.current_height {
+            current_height >= n
+        } else {
+            self.assume_height_reached
+        }
+    }
+}
+
+pub struct Older {
+    pub current_height: Option<u32>,
+    pub create_height: Option<u32>,
+    pub assume_height_reached: bool,
+}
+
+impl Older {
+    pub(crate) fn new(
+        current_height: Option<u32>,
+        create_height: Option<u32>,
+        assume_height_reached: bool,
+    ) -> Older {
+        Older {
+            current_height,
+            create_height,
+            assume_height_reached,
+        }
+    }
+}
+
+impl<Pk: MiniscriptKey> Satisfier<Pk> for Older {
+    fn check_older(&self, n: u32) -> bool {
+        if let Some(current_height) = self.current_height {
+            // TODO: test >= / >
+            current_height as u64 >= self.create_height.unwrap_or(0) as u64 + n as u64
+        } else {
+            self.assume_height_reached
+        }
+    }
+}
+
 pub struct ChunksIterator<I: Iterator> {
     iter: I,
     size: usize,
index 0722c7cf04acd9e0def8723ae0fcf9fa39638a5b..6686b84f44c23ed54a7fefad905ba7d1314dfdf4 100644 (file)
@@ -67,11 +67,11 @@ pub fn magical_blockchain_tests(attr: TokenStream, item: TokenStream) -> TokenSt
                     #parsed_sig_ident()
                 }
 
-                fn get_wallet_from_descriptors(descriptors: &(ExtendedDescriptor, Option<ExtendedDescriptor>)) -> Wallet<#return_type, MemoryDatabase> {
-                    Wallet::new(&descriptors.0.to_string(), descriptors.1.as_ref().map(|d| d.to_string()).as_deref(), Network::Regtest, MemoryDatabase::new(), get_blockchain()).unwrap()
+                fn get_wallet_from_descriptors(descriptors: &(String, Option<String>)) -> Wallet<#return_type, MemoryDatabase> {
+                    Wallet::new(&descriptors.0.to_string(), descriptors.1.as_deref(), Network::Regtest, MemoryDatabase::new(), get_blockchain()).unwrap()
                 }
 
-                fn init_single_sig() -> (Wallet<#return_type, MemoryDatabase>, (ExtendedDescriptor, Option<ExtendedDescriptor>), TestClient) {
+                fn init_single_sig() -> (Wallet<#return_type, MemoryDatabase>, (String, Option<String>), TestClient) {
                     let descriptors = testutils! {
                         @descriptors ( "wpkh(Alice)" ) ( "wpkh(Alice)" ) ( @keys ( "Alice" => (@generate_xprv "/44'/0'/0'/0/*", "/44'/0'/0'/1/*") ) )
                     };
@@ -90,6 +90,7 @@ pub fn magical_blockchain_tests(attr: TokenStream, item: TokenStream) -> TokenSt
                     let tx = testutils! {
                         @tx ( (@external descriptors, 0) => 50_000 )
                     };
+                    println!("{:?}", tx);
                     let txid = test_client.receive(tx);
 
                     wallet.sync(noop_progress(), None).unwrap();
@@ -272,6 +273,7 @@ pub fn magical_blockchain_tests(attr: TokenStream, item: TokenStream) -> TokenSt
                 #[serial]
                 fn test_sync_after_send() {
                     let (wallet, descriptors, mut test_client) = init_single_sig();
+                    println!("{}", descriptors.0);
                     let node_addr = test_client.get_node_address(None);
 
                     test_client.receive(testutils! {
@@ -284,7 +286,9 @@ pub fn magical_blockchain_tests(attr: TokenStream, item: TokenStream) -> TokenSt
                     let (psbt, details) = wallet.create_tx(TxBuilder::from_addressees(vec![(node_addr, 25_000)])).unwrap();
                     let (psbt, finalized) = wallet.sign(psbt, None).unwrap();
                     assert!(finalized, "Cannot finalize transaction");
-                    wallet.broadcast(psbt.extract_tx()).unwrap();
+                    let tx = psbt.extract_tx();
+                    println!("{}", bitcoin::consensus::encode::serialize_hex(&tx));
+                    wallet.broadcast(tx).unwrap();
 
                     wallet.sync(noop_progress(), None).unwrap();
                     assert_eq!(wallet.get_balance().unwrap(), details.received);
index a8477788deea92ac7892f3cc2a8d57962ddb933f..21495157290190ad638ddb2304a4d48c16ac787b 100644 (file)
@@ -94,10 +94,16 @@ impl TestIncomingTx {
 #[macro_export]
 macro_rules! testutils {
     ( @external $descriptors:expr, $child:expr ) => ({
-        $descriptors.0.derive($child).expect("Derivation error").address(bitcoin::Network::Regtest).expect("No address form")
+        use miniscript::descriptor::{Descriptor, DescriptorPublicKey};
+
+        let parsed = Descriptor::<DescriptorPublicKey>::parse_secret(&$descriptors.0).expect("Failed to parse descriptor in `testutils!(@external)`").0;
+        parsed.derive(&[bitcoin::util::bip32::ChildNumber::from_normal_idx($child).unwrap()]).address(bitcoin::Network::Regtest).expect("No address form")
     });
     ( @internal $descriptors:expr, $child:expr ) => ({
-        $descriptors.1.expect("Missing internal descriptor").derive($child).expect("Derivation error").address(bitcoin::Network::Regtest).expect("No address form")
+        use miniscript::descriptor::{Descriptor, DescriptorPublicKey};
+
+        let parsed = Descriptor::<DescriptorPublicKey>::parse_secret(&$descriptors.1.expect("Missing internal descriptor")).expect("Failed to parse descriptor in `testutils!(@internal)`").0;
+        parsed.derive(&[bitcoin::util::bip32::ChildNumber::from_normal_idx($child).unwrap()]).address(bitcoin::Network::Regtest).expect("No address form")
     });
     ( @e $descriptors:expr, $child:expr ) => ({ testutils!(@external $descriptors, $child) });
     ( @i $descriptors:expr, $child:expr ) => ({ testutils!(@internal $descriptors, $child) });
@@ -169,6 +175,8 @@ macro_rules! testutils {
         use std::collections::HashMap;
         use std::convert::TryInto;
 
+        use miniscript::descriptor::{Descriptor, DescriptorPublicKey};
+
         let mut keys: HashMap<&'static str, (String, Option<String>, Option<String>)> = HashMap::new();
         $(
             keys = testutils!{ @keys $( $keys )* };
@@ -189,9 +197,9 @@ macro_rules! testutils {
             }
 
         }).unwrap();
-        let external: ExtendedDescriptor = external.try_into().unwrap();
+        let external = external.to_string();
 
-        let mut internal = None::<ExtendedDescriptor>;
+        let mut internal = None::<String>;
         $(
             let string_internal: Descriptor<String> = FromStr::from_str($internal_descriptor).unwrap();
 
@@ -209,7 +217,7 @@ macro_rules! testutils {
                 }
 
             }).unwrap();
-            internal = Some(string_internal.try_into().unwrap());
+            internal = Some(string_internal.to_string());
         )*
 
         (external, internal)
@@ -349,7 +357,6 @@ impl TestClient {
         use bitcoin::blockdata::script::Builder;
         use bitcoin::blockdata::transaction::{OutPoint, TxIn, TxOut};
         use bitcoin::hash_types::{BlockHash, TxMerkleNode};
-        use bitcoin::util::hash::BitcoinHash;
 
         let block_template: serde_json::Value = self
             .call("getblocktemplate", &[json!({"rules": ["segwit"]})])
@@ -432,7 +439,7 @@ impl TestClient {
 
         self.wait_for_block(height as usize);
 
-        block.header.bitcoin_hash().to_hex()
+        block.header.block_hash().to_hex()
     }
 
     pub fn generate(&mut self, num_blocks: u64) {