Use miniscript::DescriptorPublicKey
				
					
				
			This allows us to remove all our custom "ExtendedDescriptor" implementation since that is now built directly in miniscript.
This commit is contained in:
		
							parent
							
								
									ddc2bded99
								
							
						
					
					
						commit
						5777431135
					
				| @ -2,7 +2,7 @@ | |||||||
| name = "magical-bitcoin-wallet" | name = "magical-bitcoin-wallet" | ||||||
| version = "0.1.0" | version = "0.1.0" | ||||||
| edition = "2018" | 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] | [dependencies] | ||||||
| magical-macros = { path = "./macros" } | magical-macros = { path = "./macros" } | ||||||
| @ -25,6 +25,10 @@ rocksdb = { version = "0.14", optional = true } | |||||||
| socks = { version = "0.3", optional = true } | socks = { version = "0.3", optional = true } | ||||||
| lazy_static = { version = "1.4", 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 | # Platform-specific dependencies | ||||||
| [target.'cfg(not(target_arch = "wasm32"))'.dependencies] | [target.'cfg(not(target_arch = "wasm32"))'.dependencies] | ||||||
| tokio = { version = "0.2", features = ["rt-core"] } | tokio = { version = "0.2", features = ["rt-core"] } | ||||||
|  | |||||||
| @ -1,8 +1,9 @@ | |||||||
| extern crate magical_bitcoin_wallet; | extern crate magical_bitcoin_wallet; | ||||||
| extern crate serde_json; | 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::bitcoin::*; | ||||||
| use magical_bitcoin_wallet::descriptor::*; | use magical_bitcoin_wallet::descriptor::*; | ||||||
| 
 | 
 | ||||||
| @ -14,13 +15,14 @@ fn main() { | |||||||
|                     and_v(vc:pk_h(cVt4o7BGAig1UXywgGSmARhxMdzP5qvQsxKkSsc1XEkw3tDTQFpy),older(1000))\ |                     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); |     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()); |     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); |     println!("{:?}", derived_desc); | ||||||
| 
 | 
 | ||||||
|     let addr = derived_desc.address(Network::Testnet).unwrap(); |     let addr = derived_desc.address(Network::Testnet).unwrap(); | ||||||
|  | |||||||
| @ -7,7 +7,7 @@ use std::sync::{Arc, Mutex}; | |||||||
| use log::{debug, error, info, trace}; | use log::{debug, error, info, trace}; | ||||||
| 
 | 
 | ||||||
| use bitcoin::network::message_blockdata::Inventory; | use bitcoin::network::message_blockdata::Inventory; | ||||||
| use bitcoin::{BitcoinHash, OutPoint, Transaction, Txid}; | use bitcoin::{OutPoint, Transaction, Txid}; | ||||||
| 
 | 
 | ||||||
| use rocksdb::{Options, SliceTransform, DB}; | 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 block_height = headers.get_height_for(block_hash)?.unwrap_or(0); | ||||||
|                         let saved_correct_block = match headers.get_full_block(block_height)? { |                         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, |                             _ => false, | ||||||
|                         }; |                         }; | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -16,7 +16,6 @@ use bitcoin::hash_types::FilterHash; | |||||||
| use bitcoin::hashes::hex::FromHex; | use bitcoin::hashes::hex::FromHex; | ||||||
| use bitcoin::hashes::{sha256d, Hash}; | use bitcoin::hashes::{sha256d, Hash}; | ||||||
| use bitcoin::util::bip158::BlockFilter; | use bitcoin::util::bip158::BlockFilter; | ||||||
| use bitcoin::util::hash::BitcoinHash; |  | ||||||
| use bitcoin::util::uint::Uint256; | use bitcoin::util::uint::Uint256; | ||||||
| use bitcoin::Block; | use bitcoin::Block; | ||||||
| use bitcoin::BlockHash; | use bitcoin::BlockHash; | ||||||
| @ -257,7 +256,7 @@ impl ChainStore<Full> { | |||||||
|             ); |             ); | ||||||
|             batch.put_cf( |             batch.put_cf( | ||||||
|                 cf_handle, |                 cf_handle, | ||||||
|                 StoreEntry::BlockHeaderIndex(Some(genesis.bitcoin_hash())).get_key(), |                 StoreEntry::BlockHeaderIndex(Some(genesis.block_hash())).get_key(), | ||||||
|                 &0usize.to_be_bytes(), |                 &0usize.to_be_bytes(), | ||||||
|             ); |             ); | ||||||
|             store.write(batch)?; |             store.write(batch)?; | ||||||
| @ -290,7 +289,7 @@ impl ChainStore<Full> { | |||||||
|                     .get_pinned_cf(cf_handle, StoreEntry::BlockHeader(Some(index)).get_key())? |                     .get_pinned_cf(cf_handle, StoreEntry::BlockHeader(Some(index)).get_key())? | ||||||
|                     .unwrap(), |                     .unwrap(), | ||||||
|             )?; |             )?; | ||||||
|             answer.push((header.bitcoin_hash(), index)); |             answer.push((header.block_hash(), index)); | ||||||
| 
 | 
 | ||||||
|             if let Some(new_index) = index.checked_sub(step) { |             if let Some(new_index) = index.checked_sub(step) { | ||||||
|                 index = new_index; |                 index = new_index; | ||||||
| @ -322,7 +321,7 @@ impl ChainStore<Full> { | |||||||
|         let mut batch = WriteBatch::default(); |         let mut batch = WriteBatch::default(); | ||||||
|         batch.put_cf( |         batch.put_cf( | ||||||
|             new_cf_handle, |             new_cf_handle, | ||||||
|             StoreEntry::BlockHeaderIndex(Some(header.bitcoin_hash())).get_key(), |             StoreEntry::BlockHeaderIndex(Some(header.block_hash())).get_key(), | ||||||
|             &from.to_be_bytes(), |             &from.to_be_bytes(), | ||||||
|         ); |         ); | ||||||
|         batch.put_cf( |         batch.put_cf( | ||||||
| @ -406,7 +405,7 @@ impl ChainStore<Full> { | |||||||
| 
 | 
 | ||||||
|             batch.delete_cf( |             batch.delete_cf( | ||||||
|                 cf_handle, |                 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| { |             .map(|data| { | ||||||
|                 let (header, _): (BlockHeader, Uint256) = |                 let (header, _): (BlockHeader, Uint256) = | ||||||
|                     deserialize(&data).map_err(|_| CompactFiltersError::DataCorruption)?; |                     deserialize(&data).map_err(|_| CompactFiltersError::DataCorruption)?; | ||||||
|                 Ok::<_, CompactFiltersError>(header.bitcoin_hash()) |                 Ok::<_, CompactFiltersError>(header.block_hash()) | ||||||
|             }) |             }) | ||||||
|             .transpose()?) |             .transpose()?) | ||||||
|     } |     } | ||||||
| @ -574,7 +573,7 @@ impl<T: StoreType> ChainStore<T> { | |||||||
|             .map(|(_, v)| -> Result<_, CompactFiltersError> { |             .map(|(_, v)| -> Result<_, CompactFiltersError> { | ||||||
|                 let (header, _): (BlockHeader, Uint256) = SerializeDb::deserialize(&v)?; |                 let (header, _): (BlockHeader, Uint256) = SerializeDb::deserialize(&v)?; | ||||||
| 
 | 
 | ||||||
|                 Ok(header.bitcoin_hash()) |                 Ok(header.block_hash()) | ||||||
|             }) |             }) | ||||||
|             .transpose()?) |             .transpose()?) | ||||||
|     } |     } | ||||||
| @ -593,7 +592,7 @@ impl<T: StoreType> ChainStore<T> { | |||||||
|             .get_pinned_cf(cf_handle, StoreEntry::BlockHeader(Some(from)).get_key())? |             .get_pinned_cf(cf_handle, StoreEntry::BlockHeader(Some(from)).get_key())? | ||||||
|             .map(|result| { |             .map(|result| { | ||||||
|                 let (header, work): (BlockHeader, Uint256) = SerializeDb::deserialize(&result)?; |                 let (header, work): (BlockHeader, Uint256) = SerializeDb::deserialize(&result)?; | ||||||
|                 Ok::<_, CompactFiltersError>((header.bitcoin_hash(), work)) |                 Ok::<_, CompactFiltersError>((header.block_hash(), work)) | ||||||
|             }) |             }) | ||||||
|             .transpose()? |             .transpose()? | ||||||
|             .ok_or(CompactFiltersError::DataCorruption)?; |             .ok_or(CompactFiltersError::DataCorruption)?; | ||||||
| @ -603,13 +602,13 @@ impl<T: StoreType> ChainStore<T> { | |||||||
|                 return Err(CompactFiltersError::InvalidHeaders); |                 return Err(CompactFiltersError::InvalidHeaders); | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             last_hash = header.bitcoin_hash(); |             last_hash = header.block_hash(); | ||||||
|             accumulated_work = accumulated_work + header.work(); |             accumulated_work = accumulated_work + header.work(); | ||||||
| 
 | 
 | ||||||
|             let height = from + index + 1; |             let height = from + index + 1; | ||||||
|             batch.put_cf( |             batch.put_cf( | ||||||
|                 cf_handle, |                 cf_handle, | ||||||
|                 StoreEntry::BlockHeaderIndex(Some(header.bitcoin_hash())).get_key(), |                 StoreEntry::BlockHeaderIndex(Some(header.block_hash())).get_key(), | ||||||
|                 &(height).to_be_bytes(), |                 &(height).to_be_bytes(), | ||||||
|             ); |             ); | ||||||
|             batch.put_cf( |             batch.put_cf( | ||||||
| @ -647,8 +646,8 @@ pub struct FilterHeader { | |||||||
|     filter_hash: FilterHash, |     filter_hash: FilterHash, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl BitcoinHash<FilterHeaderHash> for FilterHeader { | impl FilterHeader { | ||||||
|     fn bitcoin_hash(&self) -> FilterHeaderHash { |     fn header_hash(&self) -> FilterHeaderHash { | ||||||
|         let mut hash_data = self.filter_hash.into_inner().to_vec(); |         let mut hash_data = self.filter_hash.into_inner().to_vec(); | ||||||
|         hash_data.extend_from_slice(&self.prev_header_hash); |         hash_data.extend_from_slice(&self.prev_header_hash); | ||||||
|         sha256d::Hash::hash(&hash_data).into() |         sha256d::Hash::hash(&hash_data).into() | ||||||
| @ -794,7 +793,7 @@ impl CFStore { | |||||||
|                     prev_header_hash: last_hash, |                     prev_header_hash: last_hash, | ||||||
|                     filter_hash, |                     filter_hash, | ||||||
|                 }; |                 }; | ||||||
|                 last_hash = filter_header.bitcoin_hash(); |                 last_hash = filter_header.header_hash(); | ||||||
| 
 | 
 | ||||||
|                 filter_header |                 filter_header | ||||||
|             }) |             }) | ||||||
|  | |||||||
| @ -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")); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @ -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 |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @ -1,45 +1,275 @@ | |||||||
| use std::cell::RefCell; | use std::collections::{BTreeMap, HashMap}; | ||||||
| use std::collections::BTreeMap; |  | ||||||
| use std::convert::{Into, TryFrom}; |  | ||||||
| use std::fmt; | use std::fmt; | ||||||
| use std::str::FromStr; | use std::sync::Arc; | ||||||
| 
 | 
 | ||||||
| use bitcoin::hashes::{hash160, Hash}; | use bitcoin::hashes::hash160; | ||||||
| use bitcoin::secp256k1::{All, Secp256k1}; | use bitcoin::secp256k1::Secp256k1; | ||||||
| use bitcoin::util::bip32::{DerivationPath, ExtendedPrivKey, Fingerprint}; | use bitcoin::util::bip32::{ChildNumber, DerivationPath, Fingerprint}; | ||||||
| use bitcoin::util::psbt::PartiallySignedTransaction as PSBT; | use bitcoin::util::psbt; | ||||||
| use bitcoin::{PrivateKey, PublicKey, Script}; | use bitcoin::{PublicKey, Script, TxOut}; | ||||||
| 
 | 
 | ||||||
|  | use miniscript::descriptor::{DescriptorPublicKey, DescriptorXKey, InnerXKey}; | ||||||
| pub use miniscript::{ | 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 checksum; | ||||||
| pub mod error; | pub mod error; | ||||||
| pub mod extended_key; |  | ||||||
| mod keys; |  | ||||||
| pub mod policy; | pub mod policy; | ||||||
| 
 | 
 | ||||||
|  | // use crate::wallet::utils::AddressType;
 | ||||||
|  | use crate::wallet::signer::SignersContainer; | ||||||
|  | 
 | ||||||
| pub use self::checksum::get_checksum; | pub use self::checksum::get_checksum; | ||||||
| use self::error::Error; | use self::error::Error; | ||||||
| pub use self::extended_key::{DerivationIndex, DescriptorExtendedKey}; |  | ||||||
| pub use self::policy::Policy; | 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( |     fn extract_policy( | ||||||
|         &self, |         &self, | ||||||
|         lookup_map: &BTreeMap<String, Box<dyn Key>>, |         signers: Arc<SignersContainer<DescriptorPublicKey>>, | ||||||
|     ) -> Result<Option<Policy>, Error>; |     ) -> Result<Option<Policy>, Error>; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| pub trait ExtractPolicy { | pub trait XKeyUtils { | ||||||
|     fn extract_policy(&self) -> Result<Option<Policy>, Error>; |     fn full_path(&self, append: &[ChildNumber]) -> DerivationPath; | ||||||
|  |     fn root_fingerprint(&self) -> Fingerprint; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 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(), | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |         if self.is_wildcard { | ||||||
|  |             full_path | ||||||
|  |                 .into_iter() | ||||||
|  |                 .chain(append.into_iter()) | ||||||
|  |                 .cloned() | ||||||
|  |                 .collect() | ||||||
|  |         } else { | ||||||
|  |             full_path | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn root_fingerprint(&self) -> Fingerprint { | ||||||
|  |         match &self.source { | ||||||
|  |             &Some((fingerprint, _)) => fingerprint.clone(), | ||||||
|  |             &None => self.xkey.xkey_fingerprint(), | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 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 trait DescriptorScripts { | ||||||
|  |     fn psbt_redeem_script(&self) -> Option<Script>; | ||||||
|  |     fn psbt_witness_script(&self) -> Option<Script>; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl<T> DescriptorScripts for Descriptor<T> | ||||||
|  | where | ||||||
|  |     T: miniscript::MiniscriptKey + miniscript::ToPublicKey, | ||||||
|  | { | ||||||
|  |     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, | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn psbt_witness_script(&self) -> Option<Script> { | ||||||
|  |         match self { | ||||||
|  |             Descriptor::Wsh(ref script) => Some(script.encode()), | ||||||
|  |             Descriptor::ShWsh(ref script) => Some(script.encode()), | ||||||
|  |             _ => None, | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 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, | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn get_hd_keypaths(&self, index: u32) -> Result<HDKeyPaths, Error> { | ||||||
|  |         let mut answer = BTreeMap::new(); | ||||||
|  | 
 | ||||||
|  |         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()) }; | ||||||
|  | 
 | ||||||
|  |         self.translate_pk(translatefpk, translatefpkh)?; | ||||||
|  | 
 | ||||||
|  |         Ok(answer) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn is_fixed(&self) -> bool { | ||||||
|  |         let mut found_wildcard = false; | ||||||
|  | 
 | ||||||
|  |         let translatefpk = |key: &DescriptorPublicKey| -> Result<_, Error> { | ||||||
|  |             match key { | ||||||
|  |                 DescriptorPublicKey::PubKey(_) => {} | ||||||
|  |                 DescriptorPublicKey::XPub(xpub) => { | ||||||
|  |                     if xpub.is_wildcard { | ||||||
|  |                         found_wildcard = true; | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             Ok(DummyKey::default()) | ||||||
|  |         }; | ||||||
|  |         let translatefpkh = |_: &hash160::Hash| -> Result<_, Error> { Ok(DummyKey::default()) }; | ||||||
|  | 
 | ||||||
|  |         self.translate_pk(translatefpk, translatefpkh).unwrap(); | ||||||
|  | 
 | ||||||
|  |         !found_wildcard | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn derive_from_hd_keypaths(&self, hd_keypaths: &HDKeyPaths) -> Option<Self> { | ||||||
|  |         let index: HashMap<_, _> = hd_keypaths.values().cloned().collect(); | ||||||
|  | 
 | ||||||
|  |         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()); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             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() | ||||||
|  |                     }); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             Ok(DummyKey::default()) | ||||||
|  |         }; | ||||||
|  |         let translatefpkh = |_: &hash160::Hash| -> Result<_, Error> { Ok(DummyKey::default()) }; | ||||||
|  | 
 | ||||||
|  |         self.translate_pk(translatefpk, translatefpkh).unwrap(); | ||||||
|  | 
 | ||||||
|  |         derive_path.map(|path| self.derive(path.as_ref())) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn derive_from_psbt_input( | ||||||
|  |         &self, | ||||||
|  |         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; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         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, | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // 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,
 | ||||||
|  |     //     }
 | ||||||
|  |     // }
 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #[derive(Debug, Clone, Hash, PartialEq, PartialOrd, Eq, Ord, Default)] | #[derive(Debug, Clone, Hash, PartialEq, PartialOrd, Eq, Ord, Default)] | ||||||
| @ -67,480 +297,27 @@ impl miniscript::MiniscriptKey for DummyKey { | |||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| pub type DerivedDescriptor = Descriptor<PublicKey>; |  | ||||||
| pub type StringDescriptor = Descriptor<String>; |  | ||||||
| 
 |  | ||||||
| pub trait DescriptorMeta { |  | ||||||
|     fn is_witness(&self) -> bool; |  | ||||||
|     fn psbt_redeem_script(&self) -> Option<Script>; |  | ||||||
|     fn psbt_witness_script(&self) -> Option<Script>; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| impl<T> DescriptorMeta 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()), |  | ||||||
|             _ => None, |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     fn psbt_witness_script(&self) -> Option<Script> { |  | ||||||
|         match self { |  | ||||||
|             Descriptor::Wsh(ref script) => Some(script.encode()), |  | ||||||
|             Descriptor::ShWsh(ref script) => Some(script.encode()), |  | ||||||
|             _ => None, |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| #[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))); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         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); |  | ||||||
| 
 |  | ||||||
|             Ok(DummyKey::default()) |  | ||||||
|         }; |  | ||||||
|         let translatefpkh = |string: &String| -> Result<_, Error> { |  | ||||||
|             let (key, parsed) = Self::parse_string(string)?; |  | ||||||
|             keys.borrow_mut().insert(key, parsed); |  | ||||||
| 
 |  | ||||||
|             Ok(DummyKey::default()) |  | ||||||
|         }; |  | ||||||
| 
 |  | ||||||
|         sd.translate_pk(translatefpk, translatefpkh)?; |  | ||||||
| 
 |  | ||||||
|         Ok(ExtendedDescriptor { |  | ||||||
|             internal: sd, |  | ||||||
|             keys: keys.into_inner(), |  | ||||||
|             ctx, |  | ||||||
|         }) |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     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), |  | ||||||
|         }; |  | ||||||
| 
 |  | ||||||
|         // if !self.same_structure(&derived_desc) {
 |  | ||||||
|         //     Err(Error::CantDeriveWithMiniscript)
 |  | ||||||
|         // } else {
 |  | ||||||
|         Ok(derived_desc) |  | ||||||
|         // }
 |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     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), |  | ||||||
|         }; |  | ||||||
| 
 |  | ||||||
|         // if !self.same_structure(&derived_desc) {
 |  | ||||||
|         //     Err(Error::CantDeriveWithMiniscript)
 |  | ||||||
|         // } else {
 |  | ||||||
|         Ok(derived_desc) |  | ||||||
|         // }
 |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     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) |  | ||||||
|         }; |  | ||||||
| 
 |  | ||||||
|         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)?) |  | ||||||
|             } |  | ||||||
|         } 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)?) |  | ||||||
|             } |  | ||||||
|         } 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)) |  | ||||||
|         }; |  | ||||||
|         let translatefpkh = |  | ||||||
|             |xpub: &String| Ok(hash160::Hash::hash(&translatefpk(xpub)?.to_bytes())); |  | ||||||
| 
 |  | ||||||
|         Ok(self.internal.translate_pk(translatefpk, translatefpkh)?) |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     pub fn get_xprv(&self) -> impl IntoIterator<Item = ExtendedPrivKey> + '_ { |  | ||||||
|         self.keys |  | ||||||
|             .iter() |  | ||||||
|             .filter(|(_, v)| v.xprv().is_some()) |  | ||||||
|             .map(|(_, v)| v.xprv().unwrap()) |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     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( |  | ||||||
|         &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)); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         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, |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     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) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| impl TryFrom<&str> for ExtendedDescriptor { |  | ||||||
|     type Error = Error; |  | ||||||
| 
 |  | ||||||
|     fn try_from(value: &str) -> Result<Self, Self::Error> { |  | ||||||
|         let internal = StringDescriptor::from_str(value)?; |  | ||||||
|         ExtendedDescriptor::new(internal) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| impl TryFrom<StringDescriptor> for ExtendedDescriptor { |  | ||||||
|     type Error = Error; |  | ||||||
| 
 |  | ||||||
|     fn try_from(other: StringDescriptor) -> Result<Self, Self::Error> { |  | ||||||
|         ExtendedDescriptor::new(other) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| impl FromStr for ExtendedDescriptor { |  | ||||||
|     type Err = Error; |  | ||||||
| 
 |  | ||||||
|     fn from_str(s: &str) -> Result<Self, Self::Err> { |  | ||||||
|         Self::try_from(s) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| impl Into<String> for ExtendedDescriptor { |  | ||||||
|     fn into(self) -> String { |  | ||||||
|         format!("{}", self.internal) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| #[cfg(test)] | #[cfg(test)] | ||||||
| mod test { | mod test { | ||||||
|     use std::str::FromStr; |     use std::str::FromStr; | ||||||
| 
 | 
 | ||||||
|  |     use bitcoin::consensus::encode::deserialize; | ||||||
|     use bitcoin::hashes::hex::FromHex; |     use bitcoin::hashes::hex::FromHex; | ||||||
|     use bitcoin::{Network, PublicKey}; |     use bitcoin::util::psbt; | ||||||
| 
 | 
 | ||||||
|     use crate::descriptor::*; |     use super::*; | ||||||
| 
 |     use crate::psbt::PSBTUtils; | ||||||
|     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] |     #[test] | ||||||
|     fn test_descriptor_parse_wif() { |     fn test_derive_from_psbt_input_wpkh() { | ||||||
|         let string = "pkh(cVt4o7BGAig1UXywgGSmARhxMdzP5qvQsxKkSsc1XEkw3tDTQFpy)"; |         let psbt: psbt::PartiallySignedTransaction = deserialize(&Vec::<u8>::from_hex("70736274ff010052010000000162307be8e431fbaff807cdf9cdc3fde44d740211bc8342c31ffd6ec11fe35bcc0100000000ffffffff01328601000000000016001493ce48570b55c42c2af816aeaba06cfee1224fae000000000001011fa08601000000000016001493ce48570b55c42c2af816aeaba06cfee1224fae010304010000000000").unwrap()).unwrap(); | ||||||
|         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 |  | ||||||
|         ); |  | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
|     #[test] |         let descriptor = Descriptor::<DescriptorPublicKey>::from_str( | ||||||
|     fn test_descriptor_parse_pubkey() { |             "wpkh(02b4632d08485ff1df2db55b9dafd23347d1c47a457072a1e87be26896549a8737)", | ||||||
|         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(); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     #[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"))) |  | ||||||
|         ) |         ) | ||||||
|  |         .unwrap(); | ||||||
|  | 
 | ||||||
|  |         let result = descriptor.derive_from_psbt_input(&psbt.inputs[0], psbt.get_utxo_for(0)); | ||||||
|  |         println!("{:?}", result); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,23 +1,26 @@ | |||||||
| use std::cmp::max; | use std::cmp::max; | ||||||
| use std::collections::{BTreeMap, HashSet, VecDeque}; | use std::collections::{BTreeMap, HashSet, VecDeque}; | ||||||
|  | use std::sync::Arc; | ||||||
| 
 | 
 | ||||||
| use serde::ser::SerializeMap; | use serde::ser::SerializeMap; | ||||||
| use serde::{Serialize, Serializer}; | use serde::{Serialize, Serializer}; | ||||||
| 
 | 
 | ||||||
| use bitcoin::hashes::*; | use bitcoin::hashes::*; | ||||||
| use bitcoin::secp256k1::Secp256k1; |  | ||||||
| use bitcoin::util::bip32::Fingerprint; | use bitcoin::util::bip32::Fingerprint; | ||||||
| use bitcoin::PublicKey; | use bitcoin::PublicKey; | ||||||
| 
 | 
 | ||||||
| use miniscript::{Descriptor, Miniscript, ScriptContext, Terminal}; | use miniscript::descriptor::DescriptorPublicKey; | ||||||
|  | use miniscript::{Descriptor, Miniscript, MiniscriptKey, ScriptContext, Terminal}; | ||||||
| 
 | 
 | ||||||
| #[allow(unused_imports)] | #[allow(unused_imports)] | ||||||
| use log::{debug, error, info, trace}; | use log::{debug, error, info, trace}; | ||||||
| 
 | 
 | ||||||
|  | use crate::descriptor::ExtractPolicy; | ||||||
|  | use crate::wallet::signer::{SignerId, SignersContainer}; | ||||||
|  | 
 | ||||||
| use super::checksum::get_checksum; | use super::checksum::get_checksum; | ||||||
| use super::error::Error; | use super::error::Error; | ||||||
| use crate::descriptor::{Key, MiniscriptExtractPolicy}; | use super::XKeyUtils; | ||||||
| use crate::psbt::PSBTSatisfier; |  | ||||||
| 
 | 
 | ||||||
| #[derive(Debug, Clone, Default, Serialize)] | #[derive(Debug, Clone, Default, Serialize)] | ||||||
| pub struct PKOrF { | pub struct PKOrF { | ||||||
| @ -30,20 +33,23 @@ pub struct PKOrF { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl PKOrF { | impl PKOrF { | ||||||
|     fn from_key(k: &Box<dyn Key>) -> Self { |     fn from_key(k: &DescriptorPublicKey) -> Self { | ||||||
|         let secp = Secp256k1::gen_new(); |         match k { | ||||||
|  |             DescriptorPublicKey::PubKey(pubkey) => PKOrF { | ||||||
|  |                 pubkey: Some(*pubkey), | ||||||
|  |                 ..Default::default() | ||||||
|  |             }, | ||||||
|  |             DescriptorPublicKey::XPub(xpub) => PKOrF { | ||||||
|  |                 fingerprint: Some(xpub.root_fingerprint()), | ||||||
|  |                 ..Default::default() | ||||||
|  |             }, | ||||||
|  |         } | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|         let pubkey = k.as_public_key(&secp, None).unwrap(); |     fn from_key_hash(k: hash160::Hash) -> Self { | ||||||
|         if let Some(fing) = k.fingerprint(&secp) { |         PKOrF { | ||||||
|             PKOrF { |             pubkey_hash: Some(k), | ||||||
|                 fingerprint: Some(fing), |             ..Default::default() | ||||||
|                 ..Default::default() |  | ||||||
|             } |  | ||||||
|         } else { |  | ||||||
|             PKOrF { |  | ||||||
|                 pubkey: Some(pubkey), |  | ||||||
|                 ..Default::default() |  | ||||||
|             } |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @ -445,14 +451,15 @@ impl Policy { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     fn make_multisig( |     fn make_multisig( | ||||||
|         keys: Vec<Option<&Box<dyn Key>>>, |         keys: &Vec<DescriptorPublicKey>, | ||||||
|  |         signers: Arc<SignersContainer<DescriptorPublicKey>>, | ||||||
|         threshold: usize, |         threshold: usize, | ||||||
|     ) -> Result<Option<Policy>, PolicyError> { |     ) -> Result<Option<Policy>, PolicyError> { | ||||||
|         if threshold == 0 { |         if threshold == 0 { | ||||||
|             return Ok(None); |             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 { |         let mut contribution = Satisfaction::Partial { | ||||||
|             n: keys.len(), |             n: keys.len(), | ||||||
| @ -461,14 +468,14 @@ impl Policy { | |||||||
|             conditions: Default::default(), |             conditions: Default::default(), | ||||||
|         }; |         }; | ||||||
|         for (index, key) in keys.iter().enumerate() { |         for (index, key) in keys.iter().enumerate() { | ||||||
|             let val = if key.is_some() && key.unwrap().has_secret() { |             if let Some(_) = signers.find(signer_id(key)) { | ||||||
|                 Satisfaction::Complete { |                 contribution.add( | ||||||
|                     condition: Default::default(), |                     &Satisfaction::Complete { | ||||||
|                 } |                         condition: Default::default(), | ||||||
|             } else { |                     }, | ||||||
|                 Satisfaction::None |                     index, | ||||||
|             }; |                 )?; | ||||||
|             contribution.add(&val, index)?; |             } | ||||||
|         } |         } | ||||||
|         contribution.finalize()?; |         contribution.finalize()?; | ||||||
| 
 | 
 | ||||||
| @ -482,15 +489,6 @@ impl Policy { | |||||||
|         Ok(Some(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 { |     pub fn requires_path(&self) -> bool { | ||||||
|         self.get_requirements(&BTreeMap::new()).is_err() |         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> { | fn signer_id(key: &DescriptorPublicKey) -> SignerId<DescriptorPublicKey> { | ||||||
|     key.map(|k| { |     match key { | ||||||
|         let mut policy: Policy = SatisfiableItem::Signature(PKOrF::from_key(k)).into(); |         DescriptorPublicKey::PubKey(pubkey) => pubkey.to_pubkeyhash().into(), | ||||||
|         policy.contribution = if k.has_secret() { |         DescriptorPublicKey::XPub(xpub) => xpub.root_fingerprint().into(), | ||||||
|             Satisfaction::Complete { |     } | ||||||
|                 condition: Default::default(), |  | ||||||
|             } |  | ||||||
|         } else { |  | ||||||
|             Satisfaction::None |  | ||||||
|         }; |  | ||||||
| 
 |  | ||||||
|         policy |  | ||||||
|     }) |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| fn signature_key_from_string(key: Option<&Box<dyn Key>>) -> Option<Policy> { | fn signature( | ||||||
|     let secp = Secp256k1::gen_new(); |     key: &DescriptorPublicKey, | ||||||
|  |     signers: Arc<SignersContainer<DescriptorPublicKey>>, | ||||||
|  | ) -> Policy { | ||||||
|  |     let mut policy: Policy = SatisfiableItem::Signature(PKOrF::from_key(key)).into(); | ||||||
| 
 | 
 | ||||||
|     key.map(|k| { |     policy.contribution = if signers.find(signer_id(key)).is_some() { | ||||||
|         let pubkey = k.as_public_key(&secp, None).unwrap(); |         Satisfaction::Complete { | ||||||
|         let mut policy: Policy = if let Some(fing) = k.fingerprint(&secp) { |             condition: Default::default(), | ||||||
|             SatisfiableItem::SignatureKey(PKOrF { |  | ||||||
|                 fingerprint: Some(fing), |  | ||||||
|                 ..Default::default() |  | ||||||
|             }) |  | ||||||
|         } else { |  | ||||||
|             SatisfiableItem::SignatureKey(PKOrF { |  | ||||||
|                 pubkey_hash: Some(hash160::Hash::hash(&pubkey.to_bytes())), |  | ||||||
|                 ..Default::default() |  | ||||||
|             }) |  | ||||||
|         } |         } | ||||||
|         .into(); |     } else { | ||||||
|         policy.contribution = if k.has_secret() { |         Satisfaction::None | ||||||
|             Satisfaction::Complete { |     }; | ||||||
|                 condition: Default::default(), |  | ||||||
|             } |  | ||||||
|         } else { |  | ||||||
|             Satisfaction::None |  | ||||||
|         }; |  | ||||||
| 
 | 
 | ||||||
|         policy |     policy | ||||||
|     }) |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl<Ctx: ScriptContext> MiniscriptExtractPolicy for Miniscript<String, Ctx> { | 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(); | ||||||
|  | 
 | ||||||
|  |     if let Some(_) = signers.find(SignerId::PkHash(*key_hash)) { | ||||||
|  |         policy.contribution = Satisfaction::Complete { | ||||||
|  |             condition: Default::default(), | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     policy | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl<Ctx: ScriptContext> ExtractPolicy for Miniscript<DescriptorPublicKey, Ctx> { | ||||||
|     fn extract_policy( |     fn extract_policy( | ||||||
|         &self, |         &self, | ||||||
|         lookup_map: &BTreeMap<String, Box<dyn Key>>, |         signers: Arc<SignersContainer<DescriptorPublicKey>>, | ||||||
|     ) -> Result<Option<Policy>, Error> { |     ) -> Result<Option<Policy>, Error> { | ||||||
|         Ok(match &self.node { |         Ok(match &self.node { | ||||||
|             // Leaves
 |             // Leaves
 | ||||||
|             Terminal::True | Terminal::False => None, |             Terminal::True | Terminal::False => None, | ||||||
|             Terminal::PkK(pubkey) => signature_from_string(lookup_map.get(pubkey)), |             Terminal::PkK(pubkey) => Some(signature(pubkey, Arc::clone(&signers))), | ||||||
|             Terminal::PkH(pubkey_hash) => signature_key_from_string(lookup_map.get(pubkey_hash)), |             Terminal::PkH(pubkey_hash) => Some(signature_key(pubkey_hash, Arc::clone(&signers))), | ||||||
|             Terminal::After(value) => { |             Terminal::After(value) => { | ||||||
|                 let mut policy: Policy = SatisfiableItem::AbsoluteTimelock { value: *value }.into(); |                 let mut policy: Policy = SatisfiableItem::AbsoluteTimelock { value: *value }.into(); | ||||||
|                 policy.contribution = Satisfaction::Complete { |                 policy.contribution = Satisfaction::Complete { | ||||||
| @ -652,9 +645,7 @@ impl<Ctx: ScriptContext> MiniscriptExtractPolicy for Miniscript<String, Ctx> { | |||||||
|             Terminal::Hash160(hash) => { |             Terminal::Hash160(hash) => { | ||||||
|                 Some(SatisfiableItem::HASH160Preimage { hash: *hash }.into()) |                 Some(SatisfiableItem::HASH160Preimage { hash: *hash }.into()) | ||||||
|             } |             } | ||||||
|             Terminal::Multi(k, pks) => { |             Terminal::Multi(k, pks) => Policy::make_multisig(pks, Arc::clone(&signers), *k)?, | ||||||
|                 Policy::make_multisig(pks.iter().map(|s| lookup_map.get(s)).collect(), *k)? |  | ||||||
|             } |  | ||||||
|             // Identities
 |             // Identities
 | ||||||
|             Terminal::Alt(inner) |             Terminal::Alt(inner) | ||||||
|             | Terminal::Swap(inner) |             | Terminal::Swap(inner) | ||||||
| @ -662,26 +653,31 @@ impl<Ctx: ScriptContext> MiniscriptExtractPolicy for Miniscript<String, Ctx> { | |||||||
|             | Terminal::DupIf(inner) |             | Terminal::DupIf(inner) | ||||||
|             | Terminal::Verify(inner) |             | Terminal::Verify(inner) | ||||||
|             | Terminal::NonZero(inner) |             | Terminal::NonZero(inner) | ||||||
|             | Terminal::ZeroNotEqual(inner) => inner.extract_policy(lookup_map)?, |             | Terminal::ZeroNotEqual(inner) => inner.extract_policy(Arc::clone(&signers))?, | ||||||
|             // Complex policies
 |             // Complex policies
 | ||||||
|             Terminal::AndV(a, b) | Terminal::AndB(a, b) => { |             Terminal::AndV(a, b) | Terminal::AndB(a, b) => Policy::make_and( | ||||||
|                 Policy::make_and(a.extract_policy(lookup_map)?, b.extract_policy(lookup_map)?)? |                 a.extract_policy(Arc::clone(&signers))?, | ||||||
|             } |                 b.extract_policy(Arc::clone(&signers))?, | ||||||
|  |             )?, | ||||||
|             Terminal::AndOr(x, y, z) => Policy::make_or( |             Terminal::AndOr(x, y, z) => Policy::make_or( | ||||||
|                 Policy::make_and(x.extract_policy(lookup_map)?, y.extract_policy(lookup_map)?)?, |                 Policy::make_and( | ||||||
|                 z.extract_policy(lookup_map)?, |                     x.extract_policy(Arc::clone(&signers))?, | ||||||
|  |                     y.extract_policy(Arc::clone(&signers))?, | ||||||
|  |                 )?, | ||||||
|  |                 z.extract_policy(Arc::clone(&signers))?, | ||||||
|             )?, |             )?, | ||||||
|             Terminal::OrB(a, b) |             Terminal::OrB(a, b) | ||||||
|             | Terminal::OrD(a, b) |             | Terminal::OrD(a, b) | ||||||
|             | Terminal::OrC(a, b) |             | Terminal::OrC(a, b) | ||||||
|             | Terminal::OrI(a, b) => { |             | Terminal::OrI(a, b) => Policy::make_or( | ||||||
|                 Policy::make_or(a.extract_policy(lookup_map)?, b.extract_policy(lookup_map)?)? |                 a.extract_policy(Arc::clone(&signers))?, | ||||||
|             } |                 b.extract_policy(Arc::clone(&signers))?, | ||||||
|  |             )?, | ||||||
|             Terminal::Thresh(k, nodes) => { |             Terminal::Thresh(k, nodes) => { | ||||||
|                 let mut threshold = *k; |                 let mut threshold = *k; | ||||||
|                 let mapped: Vec<_> = nodes |                 let mapped: Vec<_> = nodes | ||||||
|                     .iter() |                     .iter() | ||||||
|                     .map(|n| n.extract_policy(lookup_map)) |                     .map(|n| n.extract_policy(Arc::clone(&signers))) | ||||||
|                     .collect::<Result<Vec<_>, _>>()? |                     .collect::<Result<Vec<_>, _>>()? | ||||||
|                     .into_iter() |                     .into_iter() | ||||||
|                     .filter_map(|x| x) |                     .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( |     fn extract_policy( | ||||||
|         &self, |         &self, | ||||||
|         lookup_map: &BTreeMap<String, Box<dyn Key>>, |         signers: Arc<SignersContainer<DescriptorPublicKey>>, | ||||||
|     ) -> Result<Option<Policy>, Error> { |     ) -> Result<Option<Policy>, Error> { | ||||||
|         match self { |         match self { | ||||||
|             Descriptor::Pk(pubkey) |             Descriptor::Pk(pubkey) | ||||||
|             | Descriptor::Pkh(pubkey) |             | Descriptor::Pkh(pubkey) | ||||||
|             | Descriptor::Wpkh(pubkey) |             | Descriptor::Wpkh(pubkey) | ||||||
|             | Descriptor::ShWpkh(pubkey) => Ok(signature_from_string(lookup_map.get(pubkey))), |             | Descriptor::ShWpkh(pubkey) => Ok(Some(signature(pubkey, signers))), | ||||||
|             Descriptor::Bare(inner) | Descriptor::Sh(inner) => { |             Descriptor::Bare(inner) | Descriptor::Sh(inner) => Ok(inner.extract_policy(signers)?), | ||||||
|                 Ok(inner.extract_policy(lookup_map)?) |             Descriptor::Wsh(inner) | Descriptor::ShWsh(inner) => Ok(inner.extract_policy(signers)?), | ||||||
|             } |  | ||||||
|             Descriptor::Wsh(inner) | Descriptor::ShWsh(inner) => { |  | ||||||
|                 Ok(inner.extract_policy(lookup_map)?) |  | ||||||
|             } |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
							
								
								
									
										13
									
								
								src/error.rs
									
									
									
									
									
								
							
							
						
						
									
										13
									
								
								src/error.rs
									
									
									
									
									
								
							| @ -1,4 +1,4 @@ | |||||||
| use bitcoin::{Address, OutPoint, Script, Txid}; | use bitcoin::{Address, OutPoint}; | ||||||
| 
 | 
 | ||||||
| #[derive(Debug)] | #[derive(Debug)] | ||||||
| pub enum Error { | pub enum Error { | ||||||
| @ -25,13 +25,7 @@ pub enum Error { | |||||||
|     SpendingPolicyRequired, |     SpendingPolicyRequired, | ||||||
|     InvalidPolicyPathError(crate::descriptor::policy::PolicyError), |     InvalidPolicyPathError(crate::descriptor::policy::PolicyError), | ||||||
| 
 | 
 | ||||||
|     // Signing errors (expected, received)
 |     Signer(crate::wallet::signer::SignerError), | ||||||
|     InputTxidMismatch((Txid, OutPoint)), |  | ||||||
|     InputRedeemScriptMismatch((Script, Script)), // scriptPubKey, redeemScript
 |  | ||||||
|     InputWitnessScriptMismatch((Script, Script)), // scriptPubKey, redeemScript
 |  | ||||||
|     InputUnknownSegwitScript(Script), |  | ||||||
|     InputMissingWitnessScript(usize), |  | ||||||
|     MissingUTXO, |  | ||||||
| 
 | 
 | ||||||
|     // Blockchain interface errors
 |     // Blockchain interface errors
 | ||||||
|     Uncapable(crate::blockchain::Capability), |     Uncapable(crate::blockchain::Capability), | ||||||
| @ -44,6 +38,7 @@ pub enum Error { | |||||||
|     Descriptor(crate::descriptor::error::Error), |     Descriptor(crate::descriptor::error::Error), | ||||||
| 
 | 
 | ||||||
|     Encode(bitcoin::consensus::encode::Error), |     Encode(bitcoin::consensus::encode::Error), | ||||||
|  |     Miniscript(miniscript::Error), | ||||||
|     BIP32(bitcoin::util::bip32::Error), |     BIP32(bitcoin::util::bip32::Error), | ||||||
|     Secp256k1(bitcoin::secp256k1::Error), |     Secp256k1(bitcoin::secp256k1::Error), | ||||||
|     JSON(serde_json::Error), |     JSON(serde_json::Error), | ||||||
| @ -75,8 +70,10 @@ impl_error!( | |||||||
|     crate::descriptor::policy::PolicyError, |     crate::descriptor::policy::PolicyError, | ||||||
|     InvalidPolicyPathError |     InvalidPolicyPathError | ||||||
| ); | ); | ||||||
|  | impl_error!(crate::wallet::signer::SignerError, Signer); | ||||||
| 
 | 
 | ||||||
| impl_error!(bitcoin::consensus::encode::Error, Encode); | impl_error!(bitcoin::consensus::encode::Error, Encode); | ||||||
|  | impl_error!(miniscript::Error, Miniscript); | ||||||
| impl_error!(bitcoin::util::bip32::Error, BIP32); | impl_error!(bitcoin::util::bip32::Error, BIP32); | ||||||
| impl_error!(bitcoin::secp256k1::Error, Secp256k1); | impl_error!(bitcoin::secp256k1::Error, Secp256k1); | ||||||
| impl_error!(serde_json::Error, JSON); | impl_error!(serde_json::Error, JSON); | ||||||
|  | |||||||
| @ -47,7 +47,6 @@ pub mod blockchain; | |||||||
| pub mod database; | pub mod database; | ||||||
| pub mod descriptor; | pub mod descriptor; | ||||||
| pub mod psbt; | pub mod psbt; | ||||||
| pub mod signer; |  | ||||||
| pub mod types; | pub mod types; | ||||||
| pub mod wallet; | pub mod wallet; | ||||||
| 
 | 
 | ||||||
|  | |||||||
							
								
								
									
										279
									
								
								src/psbt/mod.rs
									
									
									
									
									
								
							
							
						
						
									
										279
									
								
								src/psbt/mod.rs
									
									
									
									
									
								
							| @ -1,271 +1,28 @@ | |||||||
| use std::collections::BTreeMap; | use bitcoin::util::psbt::PartiallySignedTransaction as PSBT; | ||||||
|  | use bitcoin::TxOut; | ||||||
| 
 | 
 | ||||||
| use bitcoin::hashes::{hash160, Hash}; | pub trait PSBTUtils { | ||||||
| use bitcoin::util::bip143::SighashComponents; |     fn get_utxo_for(&self, input_index: usize) -> Option<TxOut>; | ||||||
| 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> { | impl PSBTUtils for PSBT { | ||||||
|     pub fn new( |     fn get_utxo_for(&self, input_index: usize) -> Option<TxOut> { | ||||||
|         input: &'a psbt::Input, |         let tx = &self.global.unsigned_tx; | ||||||
|         assume_height_reached: bool, | 
 | ||||||
|         create_height: Option<u32>, |         if input_index >= tx.input.len() { | ||||||
|         current_height: Option<u32>, |             return None; | ||||||
|     ) -> Self { |  | ||||||
|         PSBTSatisfier { |  | ||||||
|             input, |  | ||||||
|             assume_height_reached, |  | ||||||
|             create_height, |  | ||||||
|             current_height, |  | ||||||
|         } |         } | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 | 
 | ||||||
| impl<'a> PSBTSatisfier<'a> { |         if let Some(input) = self.inputs.get(input_index) { | ||||||
|     fn parse_sig(rawsig: &Vec<u8>) -> Option<BitcoinSig> { |             if let Some(wit_utxo) = &input.witness_utxo { | ||||||
|         let (flag, sig) = rawsig.split_last().unwrap(); |                 Some(wit_utxo.clone()) | ||||||
|         let flag = bitcoin::SigHashType::from_u32(*flag as u32); |             } else if let Some(in_tx) = &input.non_witness_utxo { | ||||||
|         let sig = match secp256k1::Signature::from_der(sig) { |                 Some(in_tx.output[tx.input[input_index].previous_output.vout as usize].clone()) | ||||||
|             Ok(sig) => sig, |             } else { | ||||||
|             Err(..) => return None, |                 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 { |         } else { | ||||||
|             None |             None | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 |  | ||||||
|     fn lookup_pkh_pk(&self, hash: &hash160::Hash) -> Option<bitcoin::PublicKey> { |  | ||||||
|         debug!("lookup_pkh_pk: {}", hash); |  | ||||||
| 
 |  | ||||||
|         for (pk, _) in &self.input.partial_sigs { |  | ||||||
|             if &pk.to_pubkeyhash() == hash { |  | ||||||
|                 return Some(*pk); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         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, |  | ||||||
|                 }; |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         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); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         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))) |  | ||||||
|             }) |  | ||||||
|     } |  | ||||||
| } | } | ||||||
|  | |||||||
| @ -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 |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @ -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) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @ -35,7 +35,9 @@ impl WalletExport { | |||||||
|         label: &str, |         label: &str, | ||||||
|         include_blockheight: bool, |         include_blockheight: bool, | ||||||
|     ) -> Result<Self, &'static str> { |     ) -> 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)?; |         Self::is_compatible_with_core(&descriptor)?; | ||||||
| 
 | 
 | ||||||
|         let blockheight = match wallet.database.borrow().iter_txs(false) { |         let blockheight = match wallet.database.borrow().iter_txs(false) { | ||||||
| @ -62,7 +64,7 @@ impl WalletExport { | |||||||
|             != wallet |             != wallet | ||||||
|                 .change_descriptor |                 .change_descriptor | ||||||
|                 .as_ref() |                 .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"); |             return Err("Incompatible change descriptor"); | ||||||
|         } |         } | ||||||
| @ -193,14 +195,14 @@ mod test { | |||||||
|     #[test] |     #[test] | ||||||
|     fn test_export_multi() { |     fn test_export_multi() { | ||||||
|         let descriptor = "wsh(multi(2,\ |         let descriptor = "wsh(multi(2,\ | ||||||
|                                 [73756c7f/48h/0h/0h/2h]tpubDCKxNyM3bLgbEX13Mcd8mYxbVg9ajDkWXMh29hMWBurKfVmBfWAM96QVP3zaUcN51HvkZ3ar4VwP82kC8JZhhux8vFQoJintSpVBwpFvyU3/0/*,\
 |                                 [73756c7f/48'/0'/0'/2']tpubDCKxNyM3bLgbEX13Mcd8mYxbVg9ajDkWXMh29hMWBurKfVmBfWAM96QVP3zaUcN51HvkZ3ar4VwP82kC8JZhhux8vFQoJintSpVBwpFvyU3/0/*,\
 | ||||||
|                                 [f9f62194/48h/0h/0h/2h]tpubDDp3ZSH1yCwusRppH7zgSxq2t1VEUyXSeEp8E5aFS8m43MknUjiF1bSLo3CGWAxbDyhF1XowA5ukPzyJZjznYk3kYi6oe7QxtX2euvKWsk4/0/*,\
 |                                 [f9f62194/48'/0'/0'/2']tpubDDp3ZSH1yCwusRppH7zgSxq2t1VEUyXSeEp8E5aFS8m43MknUjiF1bSLo3CGWAxbDyhF1XowA5ukPzyJZjznYk3kYi6oe7QxtX2euvKWsk4/0/*,\
 | ||||||
|                                 [c98b1535/48h/0h/0h/2h]tpubDCDi5W4sP6zSnzJeowy8rQDVhBdRARaPhK1axABi8V1661wEPeanpEXj4ZLAUEoikVtoWcyK26TKKJSecSfeKxwHCcRrge9k1ybuiL71z4a/0/*\
 |                                 [c98b1535/48'/0'/0'/2']tpubDCDi5W4sP6zSnzJeowy8rQDVhBdRARaPhK1axABi8V1661wEPeanpEXj4ZLAUEoikVtoWcyK26TKKJSecSfeKxwHCcRrge9k1ybuiL71z4a/0/*\
 | ||||||
|                           ))";
 |                           ))";
 | ||||||
|         let change_descriptor = "wsh(multi(2,\ |         let change_descriptor = "wsh(multi(2,\ | ||||||
|                                        [73756c7f/48h/0h/0h/2h]tpubDCKxNyM3bLgbEX13Mcd8mYxbVg9ajDkWXMh29hMWBurKfVmBfWAM96QVP3zaUcN51HvkZ3ar4VwP82kC8JZhhux8vFQoJintSpVBwpFvyU3/1/*,\
 |                                        [73756c7f/48'/0'/0'/2']tpubDCKxNyM3bLgbEX13Mcd8mYxbVg9ajDkWXMh29hMWBurKfVmBfWAM96QVP3zaUcN51HvkZ3ar4VwP82kC8JZhhux8vFQoJintSpVBwpFvyU3/1/*,\
 | ||||||
|                                        [f9f62194/48h/0h/0h/2h]tpubDDp3ZSH1yCwusRppH7zgSxq2t1VEUyXSeEp8E5aFS8m43MknUjiF1bSLo3CGWAxbDyhF1XowA5ukPzyJZjznYk3kYi6oe7QxtX2euvKWsk4/1/*,\
 |                                        [f9f62194/48'/0'/0'/2']tpubDDp3ZSH1yCwusRppH7zgSxq2t1VEUyXSeEp8E5aFS8m43MknUjiF1bSLo3CGWAxbDyhF1XowA5ukPzyJZjznYk3kYi6oe7QxtX2euvKWsk4/1/*,\
 | ||||||
|                                        [c98b1535/48h/0h/0h/2h]tpubDCDi5W4sP6zSnzJeowy8rQDVhBdRARaPhK1axABi8V1661wEPeanpEXj4ZLAUEoikVtoWcyK26TKKJSecSfeKxwHCcRrge9k1ybuiL71z4a/1/*\
 |                                        [c98b1535/48'/0'/0'/2']tpubDCDi5W4sP6zSnzJeowy8rQDVhBdRARaPhK1axABi8V1661wEPeanpEXj4ZLAUEoikVtoWcyK26TKKJSecSfeKxwHCcRrge9k1ybuiL71z4a/1/*\
 | ||||||
|                                  ))";
 |                                  ))";
 | ||||||
| 
 | 
 | ||||||
|         let wallet: OfflineWallet<_> = Wallet::new_offline( |         let wallet: OfflineWallet<_> = Wallet::new_offline( | ||||||
|  | |||||||
| @ -2,17 +2,14 @@ use std::cell::RefCell; | |||||||
| use std::collections::HashMap; | use std::collections::HashMap; | ||||||
| use std::collections::{BTreeMap, HashSet}; | use std::collections::{BTreeMap, HashSet}; | ||||||
| use std::ops::{Deref, DerefMut}; | 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::consensus::encode::serialize; | ||||||
|  | use bitcoin::util::bip32::ChildNumber; | ||||||
| use bitcoin::util::psbt::PartiallySignedTransaction as PSBT; | use bitcoin::util::psbt::PartiallySignedTransaction as PSBT; | ||||||
| use bitcoin::{ | use bitcoin::{Address, Network, OutPoint, Script, SigHashType, Transaction, TxOut, Txid}; | ||||||
|     Address, Network, OutPoint, PublicKey, Script, SigHashType, Transaction, TxOut, Txid, |  | ||||||
| }; |  | ||||||
| 
 | 
 | ||||||
| use miniscript::BitcoinSig; | use miniscript::descriptor::DescriptorPublicKey; | ||||||
| 
 | 
 | ||||||
| #[allow(unused_imports)] | #[allow(unused_imports)] | ||||||
| use log::{debug, error, info, trace}; | use log::{debug, error, info, trace}; | ||||||
| @ -20,19 +17,23 @@ use log::{debug, error, info, trace}; | |||||||
| pub mod coin_selection; | pub mod coin_selection; | ||||||
| pub mod export; | pub mod export; | ||||||
| mod rbf; | mod rbf; | ||||||
|  | pub mod signer; | ||||||
| pub mod time; | pub mod time; | ||||||
| pub mod tx_builder; | pub mod tx_builder; | ||||||
| pub mod utils; | pub mod utils; | ||||||
| 
 | 
 | ||||||
|  | use signer::{Signer, SignersContainer}; | ||||||
| use tx_builder::TxBuilder; | use tx_builder::TxBuilder; | ||||||
| use utils::{FeeRate, IsDust}; | use utils::{After, FeeRate, IsDust, Older}; | ||||||
| 
 | 
 | ||||||
| use crate::blockchain::{Blockchain, OfflineBlockchain, OnlineBlockchain, Progress}; | use crate::blockchain::{Blockchain, OfflineBlockchain, OnlineBlockchain, Progress}; | ||||||
| use crate::database::{BatchDatabase, BatchOperations, DatabaseUtils}; | 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::error::Error; | ||||||
| use crate::psbt::{utils::PSBTUtils, PSBTSatisfier, PSBTSigner}; | use crate::psbt::PSBTUtils; | ||||||
| use crate::signer::Signer; | // use crate::psbt::{utils::PSBTUtils, PSBTSatisfier, PSBTSigner};
 | ||||||
| use crate::types::*; | use crate::types::*; | ||||||
| 
 | 
 | ||||||
| const CACHE_ADDR_BATCH_SIZE: u32 = 100; | 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> { | pub struct Wallet<B: Blockchain, D: BatchDatabase> { | ||||||
|     descriptor: ExtendedDescriptor, |     descriptor: ExtendedDescriptor, | ||||||
|     change_descriptor: Option<ExtendedDescriptor>, |     change_descriptor: Option<ExtendedDescriptor>, | ||||||
|  | 
 | ||||||
|  |     signers: Arc<SignersContainer<DescriptorPublicKey>>, | ||||||
|  |     change_signers: Arc<SignersContainer<DescriptorPublicKey>>, | ||||||
|  | 
 | ||||||
|     network: Network, |     network: Network, | ||||||
| 
 | 
 | ||||||
|     current_height: Option<u32>, |     current_height: Option<u32>, | ||||||
| @ -66,27 +71,32 @@ where | |||||||
|             ScriptType::External, |             ScriptType::External, | ||||||
|             get_checksum(descriptor)?.as_bytes(), |             get_checksum(descriptor)?.as_bytes(), | ||||||
|         )?; |         )?; | ||||||
|         let descriptor = ExtendedDescriptor::from_str(descriptor)?; |         let (descriptor, keymap) = ExtendedDescriptor::parse_secret(descriptor)?; | ||||||
|         let change_descriptor = match change_descriptor { |         let signers = Arc::new(SignersContainer::from(keymap)); | ||||||
|  |         let (change_descriptor, change_signers) = match change_descriptor { | ||||||
|             Some(desc) => { |             Some(desc) => { | ||||||
|                 database.check_descriptor_checksum( |                 database.check_descriptor_checksum( | ||||||
|                     ScriptType::Internal, |                     ScriptType::Internal, | ||||||
|                     get_checksum(desc)?.as_bytes(), |                     get_checksum(desc)?.as_bytes(), | ||||||
|                 )?; |                 )?; | ||||||
| 
 | 
 | ||||||
|                 let parsed = ExtendedDescriptor::from_str(desc)?; |                 let (change_descriptor, change_keymap) = ExtendedDescriptor::parse_secret(desc)?; | ||||||
|                 if !parsed.same_structure(descriptor.as_ref()) { |                 let change_signers = Arc::new(SignersContainer::from(change_keymap)); | ||||||
|                     return Err(Error::DifferentDescriptorStructure); |                 // 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 { |         Ok(Wallet { | ||||||
|             descriptor, |             descriptor, | ||||||
|             change_descriptor, |             change_descriptor, | ||||||
|  |             signers, | ||||||
|  |             change_signers, | ||||||
|  | 
 | ||||||
|             network, |             network, | ||||||
| 
 | 
 | ||||||
|             current_height: None, |             current_height: None, | ||||||
| @ -100,7 +110,7 @@ where | |||||||
|         let index = self.fetch_and_increment_index(ScriptType::External)?; |         let index = self.fetch_and_increment_index(ScriptType::External)?; | ||||||
| 
 | 
 | ||||||
|         self.descriptor |         self.descriptor | ||||||
|             .derive(index)? |             .derive(&[ChildNumber::from_normal_idx(index).unwrap()]) | ||||||
|             .address(self.network) |             .address(self.network) | ||||||
|             .ok_or(Error::ScriptDoesntHaveAddressForm) |             .ok_or(Error::ScriptDoesntHaveAddressForm) | ||||||
|     } |     } | ||||||
| @ -133,7 +143,10 @@ where | |||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         // TODO: fetch both internal and external policies
 |         // 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() { |         if policy.requires_path() && builder.policy_path.is_none() { | ||||||
|             return Err(Error::SpendingPolicyRequired); |             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
 |         // TODO: use the right weight instead of the maximum, and only fall-back to it if the
 | ||||||
|         // script is unknown in the database
 |         // script is unknown in the database
 | ||||||
|         let input_witness_weight = std::cmp::max( |         let input_witness_weight = std::cmp::max( | ||||||
|             self.get_descriptor_for(ScriptType::Internal) |             self.get_descriptor_for_script_type(ScriptType::Internal) | ||||||
|                 .0 |                 .0 | ||||||
|                 .max_satisfaction_weight(), |                 .max_satisfaction_weight(), | ||||||
|             self.get_descriptor_for(ScriptType::External) |             self.get_descriptor_for_script_type(ScriptType::External) | ||||||
|                 .0 |                 .0 | ||||||
|                 .max_satisfaction_weight(), |                 .max_satisfaction_weight(), | ||||||
|         ); |         ); | ||||||
| @ -369,7 +382,7 @@ where | |||||||
|                 // `get_deget_descriptor_for` to find what's the ScriptType for `Internal`
 |                 // `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
 |                 // addresses really is, because if there's no change_descriptor it's actually equal
 | ||||||
|                 // to "External"
 |                 // to "External"
 | ||||||
|                 let (_, change_type) = self.get_descriptor_for(ScriptType::Internal); |                 let (_, change_type) = self.get_descriptor_for_script_type(ScriptType::Internal); | ||||||
|                 match self |                 match self | ||||||
|                     .database |                     .database | ||||||
|                     .borrow() |                     .borrow() | ||||||
| @ -435,10 +448,10 @@ where | |||||||
|                     // TODO: use the right weight instead of the maximum, and only fall-back to it if the
 |                     // TODO: use the right weight instead of the maximum, and only fall-back to it if the
 | ||||||
|                     // script is unknown in the database
 |                     // script is unknown in the database
 | ||||||
|                     let input_witness_weight = std::cmp::max( |                     let input_witness_weight = std::cmp::max( | ||||||
|                         self.get_descriptor_for(ScriptType::Internal) |                         self.get_descriptor_for_script_type(ScriptType::Internal) | ||||||
|                             .0 |                             .0 | ||||||
|                             .max_satisfaction_weight(), |                             .max_satisfaction_weight(), | ||||||
|                         self.get_descriptor_for(ScriptType::External) |                         self.get_descriptor_for_script_type(ScriptType::External) | ||||||
|                             .0 |                             .0 | ||||||
|                             .max_satisfaction_weight(), |                             .max_satisfaction_weight(), | ||||||
|                     ); |                     ); | ||||||
| @ -545,139 +558,11 @@ where | |||||||
|         // this helps us doing our job later
 |         // this helps us doing our job later
 | ||||||
|         self.add_input_hd_keypaths(&mut psbt)?; |         self.add_input_hd_keypaths(&mut psbt)?; | ||||||
| 
 | 
 | ||||||
|         let tx = &psbt.global.unsigned_tx; |         for index in 0..psbt.inputs.len() { | ||||||
| 
 |             self.signers.sign(&mut psbt, index)?; | ||||||
|         let mut signer = PSBTSigner::from_descriptor(&psbt.global.unsigned_tx, &self.descriptor)?; |             if self.change_descriptor.is_some() { | ||||||
|         if let Some(desc) = &self.change_descriptor { |                 self.change_signers.sign(&mut psbt, index)?; | ||||||
|             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); |  | ||||||
|                 } |  | ||||||
|             } |             } | ||||||
| 
 |  | ||||||
|             // push all the signatures into the psbt
 |  | ||||||
|             input.partial_sigs.append(&mut partial_sigs); |  | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         // attempt to finalize
 |         // attempt to finalize
 | ||||||
| @ -688,9 +573,13 @@ where | |||||||
| 
 | 
 | ||||||
|     pub fn policies(&self, script_type: ScriptType) -> Result<Option<Policy>, Error> { |     pub fn policies(&self, script_type: ScriptType) -> Result<Option<Policy>, Error> { | ||||||
|         match (script_type, self.change_descriptor.as_ref()) { |         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, 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, |         script_type: ScriptType, | ||||||
|     ) -> Result<Option<ExtendedDescriptor>, Error> { |     ) -> Result<Option<ExtendedDescriptor>, Error> { | ||||||
|         match (script_type, self.change_descriptor.as_ref()) { |         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, 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> { |     ) -> Result<bool, Error> { | ||||||
|         let mut tx = psbt.global.unsigned_tx.clone(); |         let mut tx = psbt.global.unsigned_tx.clone(); | ||||||
| 
 | 
 | ||||||
|         for (n, input) in tx.input.iter_mut().enumerate() { |         for (n, (input, psbt_input)) in tx.input.iter_mut().zip(psbt.inputs.iter()).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, |  | ||||||
|             }; |  | ||||||
| 
 |  | ||||||
|             // if the height is None in the database it means it's still unconfirmed, so consider
 |             // if the height is None in the database it means it's still unconfirmed, so consider
 | ||||||
|             // that as a very high value
 |             // that as a very high value
 | ||||||
|             let create_height = self |             let create_height = self | ||||||
| @ -738,10 +616,43 @@ where | |||||||
|                 n, input.previous_output, create_height, current_height |                 n, input.previous_output, create_height, current_height | ||||||
|             ); |             ); | ||||||
| 
 | 
 | ||||||
|             let satisfier = |             // - Try to derive the descriptor by looking at the txout. If it's in our database, we
 | ||||||
|                 PSBTSatisfier::new(&psbt.inputs[n], false, create_height, current_height); |             //   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, |                 Ok(_) => continue, | ||||||
|                 Err(e) => { |                 Err(e) => { | ||||||
|                     debug!("satisfy error {:?} for input {}", e, n); |                     debug!("satisfy error {:?} for input {}", e, n); | ||||||
| @ -761,7 +672,10 @@ where | |||||||
| 
 | 
 | ||||||
|     // Internals
 |     // 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 { |         let desc = match script_type { | ||||||
|             ScriptType::Internal if self.change_descriptor.is_some() => ( |             ScriptType::Internal if self.change_descriptor.is_some() => ( | ||||||
|                 self.change_descriptor.as_ref().unwrap(), |                 self.change_descriptor.as_ref().unwrap(), | ||||||
| @ -773,25 +687,26 @@ where | |||||||
|         desc |         desc | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     fn to_p2pkh(&self, pubkey_hash: &[u8]) -> Script { |     fn get_descriptor_for_txout(&self, txout: &TxOut) -> Result<Option<ExtendedDescriptor>, Error> { | ||||||
|         Builder::new() |         Ok(self | ||||||
|             .push_opcode(opcodes::all::OP_DUP) |             .database | ||||||
|             .push_opcode(opcodes::all::OP_HASH160) |             .borrow() | ||||||
|             .push_slice(pubkey_hash) |             .get_path_from_script_pubkey(&txout.script_pubkey)? | ||||||
|             .push_opcode(opcodes::all::OP_EQUALVERIFY) |             .map(|(script_type, child)| (self.get_descriptor_for_script_type(script_type).0, child)) | ||||||
|             .push_opcode(opcodes::all::OP_CHECKSIG) |             .map(|(desc, child)| desc.derive(&[ChildNumber::from_normal_idx(child).unwrap()]))) | ||||||
|             .into_script() |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     fn get_change_address(&self) -> Result<Script, Error> { |     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)?; |         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> { |     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() { |         let index = match descriptor.is_fixed() { | ||||||
|             true => 0, |             true => 0, | ||||||
|             false => self |             false => self | ||||||
| @ -818,7 +733,7 @@ where | |||||||
|         from: u32, |         from: u32, | ||||||
|         mut count: u32, |         mut count: u32, | ||||||
|     ) -> Result<(), Error> { |     ) -> 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 descriptor.is_fixed() { | ||||||
|             if from > 0 { |             if from > 0 { | ||||||
|                 return Ok(()); |                 return Ok(()); | ||||||
| @ -832,7 +747,9 @@ where | |||||||
|         let start_time = time::Instant::new(); |         let start_time = time::Instant::new(); | ||||||
|         for i in from..(from + count) { |         for i in from..(from + count) { | ||||||
|             address_batch.set_script_pubkey( |             address_batch.set_script_pubkey( | ||||||
|                 &descriptor.derive(i)?.script_pubkey(), |                 &descriptor | ||||||
|  |                     .derive(&[ChildNumber::from_normal_idx(i).unwrap()]) | ||||||
|  |                     .script_pubkey(), | ||||||
|                 script_type, |                 script_type, | ||||||
|                 i, |                 i, | ||||||
|             )?; |             )?; | ||||||
| @ -924,9 +841,9 @@ where | |||||||
|                 None => continue, |                 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)?; |             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.redeem_script = derived_descriptor.psbt_redeem_script(); | ||||||
|             psbt_input.witness_script = derived_descriptor.psbt_witness_script(); |             psbt_input.witness_script = derived_descriptor.psbt_witness_script(); | ||||||
| @ -957,7 +874,7 @@ where | |||||||
|                 .borrow() |                 .borrow() | ||||||
|                 .get_path_from_script_pubkey(&tx_output.script_pubkey)? |                 .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)?; |                 psbt_output.hd_keypaths = desc.get_hd_keypaths(child)?; | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| @ -982,7 +899,7 @@ where | |||||||
|                     debug!("Found descriptor {:?}/{}", script_type, child); |                     debug!("Found descriptor {:?}/{}", script_type, child); | ||||||
| 
 | 
 | ||||||
|                     // merge hd_keypaths
 |                     // 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)?; |                     let mut hd_keypaths = desc.get_hd_keypaths(child)?; | ||||||
|                     psbt_input.hd_keypaths.append(&mut hd_keypaths); |                     psbt_input.hd_keypaths.append(&mut hd_keypaths); | ||||||
|                 } |                 } | ||||||
| @ -1086,13 +1003,12 @@ where | |||||||
| 
 | 
 | ||||||
| #[cfg(test)] | #[cfg(test)] | ||||||
| mod test { | mod test { | ||||||
|     use bitcoin::Network; |     use std::str::FromStr; | ||||||
| 
 | 
 | ||||||
|     use miniscript::Descriptor; |     use bitcoin::Network; | ||||||
| 
 | 
 | ||||||
|     use crate::database::memory::MemoryDatabase; |     use crate::database::memory::MemoryDatabase; | ||||||
|     use crate::database::Database; |     use crate::database::Database; | ||||||
|     use crate::descriptor::ExtendedDescriptor; |  | ||||||
|     use crate::types::ScriptType; |     use crate::types::ScriptType; | ||||||
| 
 | 
 | ||||||
|     use super::*; |     use super::*; | ||||||
| @ -1205,12 +1121,12 @@ mod test { | |||||||
|         descriptor: &str, |         descriptor: &str, | ||||||
|     ) -> ( |     ) -> ( | ||||||
|         OfflineWallet<MemoryDatabase>, |         OfflineWallet<MemoryDatabase>, | ||||||
|         (ExtendedDescriptor, Option<ExtendedDescriptor>), |         (String, Option<String>), | ||||||
|         bitcoin::Txid, |         bitcoin::Txid, | ||||||
|     ) { |     ) { | ||||||
|         let descriptors = testutils!(@descriptors (descriptor)); |         let descriptors = testutils!(@descriptors (descriptor)); | ||||||
|         let wallet: OfflineWallet<_> = Wallet::new_offline( |         let wallet: OfflineWallet<_> = Wallet::new_offline( | ||||||
|             &descriptors.0.to_string(), |             &descriptors.0, | ||||||
|             None, |             None, | ||||||
|             Network::Regtest, |             Network::Regtest, | ||||||
|             MemoryDatabase::new(), |             MemoryDatabase::new(), | ||||||
|  | |||||||
							
								
								
									
										327
									
								
								src/wallet/signer.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										327
									
								
								src/wallet/signer.rs
									
									
									
									
									
										Normal file
									
								
							| @ -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, | ||||||
|  |         )) | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -1,3 +1,5 @@ | |||||||
|  | use miniscript::{MiniscriptKey, Satisfier}; | ||||||
|  | 
 | ||||||
| // De-facto standard "dust limit" (even though it should change based on the output type)
 | // De-facto standard "dust limit" (even though it should change based on the output type)
 | ||||||
| const DUST_LIMIT_SATOSHI: u64 = 546; | 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> { | pub struct ChunksIterator<I: Iterator> { | ||||||
|     iter: I, |     iter: I, | ||||||
|     size: usize, |     size: usize, | ||||||
|  | |||||||
| @ -67,11 +67,11 @@ pub fn magical_blockchain_tests(attr: TokenStream, item: TokenStream) -> TokenSt | |||||||
|                     #parsed_sig_ident() |                     #parsed_sig_ident() | ||||||
|                 } |                 } | ||||||
| 
 | 
 | ||||||
|                 fn get_wallet_from_descriptors(descriptors: &(ExtendedDescriptor, Option<ExtendedDescriptor>)) -> Wallet<#return_type, MemoryDatabase> { |                 fn get_wallet_from_descriptors(descriptors: &(String, Option<String>)) -> 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() |                     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! { |                     let descriptors = testutils! { | ||||||
|                         @descriptors ( "wpkh(Alice)" ) ( "wpkh(Alice)" ) ( @keys ( "Alice" => (@generate_xprv "/44'/0'/0'/0/*", "/44'/0'/0'/1/*") ) ) |                         @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! { |                     let tx = testutils! { | ||||||
|                         @tx ( (@external descriptors, 0) => 50_000 ) |                         @tx ( (@external descriptors, 0) => 50_000 ) | ||||||
|                     }; |                     }; | ||||||
|  |                     println!("{:?}", tx); | ||||||
|                     let txid = test_client.receive(tx); |                     let txid = test_client.receive(tx); | ||||||
| 
 | 
 | ||||||
|                     wallet.sync(noop_progress(), None).unwrap(); |                     wallet.sync(noop_progress(), None).unwrap(); | ||||||
| @ -272,6 +273,7 @@ pub fn magical_blockchain_tests(attr: TokenStream, item: TokenStream) -> TokenSt | |||||||
|                 #[serial] |                 #[serial] | ||||||
|                 fn test_sync_after_send() { |                 fn test_sync_after_send() { | ||||||
|                     let (wallet, descriptors, mut test_client) = init_single_sig(); |                     let (wallet, descriptors, mut test_client) = init_single_sig(); | ||||||
|  |                     println!("{}", descriptors.0); | ||||||
|                     let node_addr = test_client.get_node_address(None); |                     let node_addr = test_client.get_node_address(None); | ||||||
| 
 | 
 | ||||||
|                     test_client.receive(testutils! { |                     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, details) = wallet.create_tx(TxBuilder::from_addressees(vec![(node_addr, 25_000)])).unwrap(); | ||||||
|                     let (psbt, finalized) = wallet.sign(psbt, None).unwrap(); |                     let (psbt, finalized) = wallet.sign(psbt, None).unwrap(); | ||||||
|                     assert!(finalized, "Cannot finalize transaction"); |                     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(); |                     wallet.sync(noop_progress(), None).unwrap(); | ||||||
|                     assert_eq!(wallet.get_balance().unwrap(), details.received); |                     assert_eq!(wallet.get_balance().unwrap(), details.received); | ||||||
|  | |||||||
| @ -94,10 +94,16 @@ impl TestIncomingTx { | |||||||
| #[macro_export] | #[macro_export] | ||||||
| macro_rules! testutils { | macro_rules! testutils { | ||||||
|     ( @external $descriptors:expr, $child:expr ) => ({ |     ( @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 ) => ({ |     ( @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) }); |     ( @e $descriptors:expr, $child:expr ) => ({ testutils!(@external $descriptors, $child) }); | ||||||
|     ( @i $descriptors:expr, $child:expr ) => ({ testutils!(@internal $descriptors, $child) }); |     ( @i $descriptors:expr, $child:expr ) => ({ testutils!(@internal $descriptors, $child) }); | ||||||
| @ -169,6 +175,8 @@ macro_rules! testutils { | |||||||
|         use std::collections::HashMap; |         use std::collections::HashMap; | ||||||
|         use std::convert::TryInto; |         use std::convert::TryInto; | ||||||
| 
 | 
 | ||||||
|  |         use miniscript::descriptor::{Descriptor, DescriptorPublicKey}; | ||||||
|  | 
 | ||||||
|         let mut keys: HashMap<&'static str, (String, Option<String>, Option<String>)> = HashMap::new(); |         let mut keys: HashMap<&'static str, (String, Option<String>, Option<String>)> = HashMap::new(); | ||||||
|         $( |         $( | ||||||
|             keys = testutils!{ @keys $( $keys )* }; |             keys = testutils!{ @keys $( $keys )* }; | ||||||
| @ -189,9 +197,9 @@ macro_rules! testutils { | |||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|         }).unwrap(); |         }).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(); |             let string_internal: Descriptor<String> = FromStr::from_str($internal_descriptor).unwrap(); | ||||||
| 
 | 
 | ||||||
| @ -209,7 +217,7 @@ macro_rules! testutils { | |||||||
|                 } |                 } | ||||||
| 
 | 
 | ||||||
|             }).unwrap(); |             }).unwrap(); | ||||||
|             internal = Some(string_internal.try_into().unwrap()); |             internal = Some(string_internal.to_string()); | ||||||
|         )* |         )* | ||||||
| 
 | 
 | ||||||
|         (external, internal) |         (external, internal) | ||||||
| @ -349,7 +357,6 @@ impl TestClient { | |||||||
|         use bitcoin::blockdata::script::Builder; |         use bitcoin::blockdata::script::Builder; | ||||||
|         use bitcoin::blockdata::transaction::{OutPoint, TxIn, TxOut}; |         use bitcoin::blockdata::transaction::{OutPoint, TxIn, TxOut}; | ||||||
|         use bitcoin::hash_types::{BlockHash, TxMerkleNode}; |         use bitcoin::hash_types::{BlockHash, TxMerkleNode}; | ||||||
|         use bitcoin::util::hash::BitcoinHash; |  | ||||||
| 
 | 
 | ||||||
|         let block_template: serde_json::Value = self |         let block_template: serde_json::Value = self | ||||||
|             .call("getblocktemplate", &[json!({"rules": ["segwit"]})]) |             .call("getblocktemplate", &[json!({"rules": ["segwit"]})]) | ||||||
| @ -432,7 +439,7 @@ impl TestClient { | |||||||
| 
 | 
 | ||||||
|         self.wait_for_block(height as usize); |         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) { |     pub fn generate(&mut self, num_blocks: u64) { | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user