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" | ||||
| version = "0.1.0" | ||||
| edition = "2018" | ||||
| authors = ["Riccardo Casatta <riccardo@casatta.it>", "Alekos Filini <alekos.filini@gmail.com>"] | ||||
| authors = ["Alekos Filini <alekos.filini@gmail.com>", "Riccardo Casatta <riccardo@casatta.it>"] | ||||
| 
 | ||||
| [dependencies] | ||||
| magical-macros = { path = "./macros" } | ||||
| @ -25,6 +25,10 @@ rocksdb = { version = "0.14", optional = true } | ||||
| socks = { version = "0.3", optional = true } | ||||
| lazy_static = { version = "1.4", optional = true } | ||||
| 
 | ||||
| [patch.crates-io] | ||||
| bitcoin = { git = "https://github.com/rust-bitcoin/rust-bitcoin/", rev = "478e091" } | ||||
| miniscript = { git = "https://github.com/MagicalBitcoin/rust-miniscript", branch = "descriptor-public-key" } | ||||
| 
 | ||||
| # Platform-specific dependencies | ||||
| [target.'cfg(not(target_arch = "wasm32"))'.dependencies] | ||||
| tokio = { version = "0.2", features = ["rt-core"] } | ||||
|  | ||||
| @ -1,8 +1,9 @@ | ||||
| extern crate magical_bitcoin_wallet; | ||||
| extern crate serde_json; | ||||
| 
 | ||||
| use std::str::FromStr; | ||||
| use std::sync::Arc; | ||||
| 
 | ||||
| use magical_bitcoin_wallet::bitcoin::util::bip32::ChildNumber; | ||||
| use magical_bitcoin_wallet::bitcoin::*; | ||||
| use magical_bitcoin_wallet::descriptor::*; | ||||
| 
 | ||||
| @ -14,13 +15,14 @@ fn main() { | ||||
|                     and_v(vc:pk_h(cVt4o7BGAig1UXywgGSmARhxMdzP5qvQsxKkSsc1XEkw3tDTQFpy),older(1000))\ | ||||
|                    ))";
 | ||||
| 
 | ||||
|     let extended_desc = ExtendedDescriptor::from_str(desc).unwrap(); | ||||
|     let (extended_desc, key_map) = ExtendedDescriptor::parse_secret(desc).unwrap(); | ||||
|     println!("{:?}", extended_desc); | ||||
| 
 | ||||
|     let policy = extended_desc.extract_policy().unwrap(); | ||||
|     let signers = Arc::new(key_map.into()); | ||||
|     let policy = extended_desc.extract_policy(signers).unwrap(); | ||||
|     println!("policy: {}", serde_json::to_string(&policy).unwrap()); | ||||
| 
 | ||||
|     let derived_desc = extended_desc.derive(42).unwrap(); | ||||
|     let derived_desc = extended_desc.derive(&[ChildNumber::from_normal_idx(42).unwrap()]); | ||||
|     println!("{:?}", derived_desc); | ||||
| 
 | ||||
|     let addr = derived_desc.address(Network::Testnet).unwrap(); | ||||
|  | ||||
| @ -7,7 +7,7 @@ use std::sync::{Arc, Mutex}; | ||||
| use log::{debug, error, info, trace}; | ||||
| 
 | ||||
| use bitcoin::network::message_blockdata::Inventory; | ||||
| use bitcoin::{BitcoinHash, OutPoint, Transaction, Txid}; | ||||
| use bitcoin::{OutPoint, Transaction, Txid}; | ||||
| 
 | ||||
| use rocksdb::{Options, SliceTransform, DB}; | ||||
| 
 | ||||
| @ -274,7 +274,7 @@ impl OnlineBlockchain for CompactFiltersBlockchain { | ||||
| 
 | ||||
|                         let block_height = headers.get_height_for(block_hash)?.unwrap_or(0); | ||||
|                         let saved_correct_block = match headers.get_full_block(block_height)? { | ||||
|                             Some(block) if &block.bitcoin_hash() == block_hash => true, | ||||
|                             Some(block) if &block.block_hash() == block_hash => true, | ||||
|                             _ => false, | ||||
|                         }; | ||||
| 
 | ||||
|  | ||||
| @ -16,7 +16,6 @@ use bitcoin::hash_types::FilterHash; | ||||
| use bitcoin::hashes::hex::FromHex; | ||||
| use bitcoin::hashes::{sha256d, Hash}; | ||||
| use bitcoin::util::bip158::BlockFilter; | ||||
| use bitcoin::util::hash::BitcoinHash; | ||||
| use bitcoin::util::uint::Uint256; | ||||
| use bitcoin::Block; | ||||
| use bitcoin::BlockHash; | ||||
| @ -257,7 +256,7 @@ impl ChainStore<Full> { | ||||
|             ); | ||||
|             batch.put_cf( | ||||
|                 cf_handle, | ||||
|                 StoreEntry::BlockHeaderIndex(Some(genesis.bitcoin_hash())).get_key(), | ||||
|                 StoreEntry::BlockHeaderIndex(Some(genesis.block_hash())).get_key(), | ||||
|                 &0usize.to_be_bytes(), | ||||
|             ); | ||||
|             store.write(batch)?; | ||||
| @ -290,7 +289,7 @@ impl ChainStore<Full> { | ||||
|                     .get_pinned_cf(cf_handle, StoreEntry::BlockHeader(Some(index)).get_key())? | ||||
|                     .unwrap(), | ||||
|             )?; | ||||
|             answer.push((header.bitcoin_hash(), index)); | ||||
|             answer.push((header.block_hash(), index)); | ||||
| 
 | ||||
|             if let Some(new_index) = index.checked_sub(step) { | ||||
|                 index = new_index; | ||||
| @ -322,7 +321,7 @@ impl ChainStore<Full> { | ||||
|         let mut batch = WriteBatch::default(); | ||||
|         batch.put_cf( | ||||
|             new_cf_handle, | ||||
|             StoreEntry::BlockHeaderIndex(Some(header.bitcoin_hash())).get_key(), | ||||
|             StoreEntry::BlockHeaderIndex(Some(header.block_hash())).get_key(), | ||||
|             &from.to_be_bytes(), | ||||
|         ); | ||||
|         batch.put_cf( | ||||
| @ -406,7 +405,7 @@ impl ChainStore<Full> { | ||||
| 
 | ||||
|             batch.delete_cf( | ||||
|                 cf_handle, | ||||
|                 StoreEntry::BlockHeaderIndex(Some(header.bitcoin_hash())).get_key(), | ||||
|                 StoreEntry::BlockHeaderIndex(Some(header.block_hash())).get_key(), | ||||
|             ); | ||||
|         } | ||||
| 
 | ||||
| @ -461,7 +460,7 @@ impl ChainStore<Full> { | ||||
|             .map(|data| { | ||||
|                 let (header, _): (BlockHeader, Uint256) = | ||||
|                     deserialize(&data).map_err(|_| CompactFiltersError::DataCorruption)?; | ||||
|                 Ok::<_, CompactFiltersError>(header.bitcoin_hash()) | ||||
|                 Ok::<_, CompactFiltersError>(header.block_hash()) | ||||
|             }) | ||||
|             .transpose()?) | ||||
|     } | ||||
| @ -574,7 +573,7 @@ impl<T: StoreType> ChainStore<T> { | ||||
|             .map(|(_, v)| -> Result<_, CompactFiltersError> { | ||||
|                 let (header, _): (BlockHeader, Uint256) = SerializeDb::deserialize(&v)?; | ||||
| 
 | ||||
|                 Ok(header.bitcoin_hash()) | ||||
|                 Ok(header.block_hash()) | ||||
|             }) | ||||
|             .transpose()?) | ||||
|     } | ||||
| @ -593,7 +592,7 @@ impl<T: StoreType> ChainStore<T> { | ||||
|             .get_pinned_cf(cf_handle, StoreEntry::BlockHeader(Some(from)).get_key())? | ||||
|             .map(|result| { | ||||
|                 let (header, work): (BlockHeader, Uint256) = SerializeDb::deserialize(&result)?; | ||||
|                 Ok::<_, CompactFiltersError>((header.bitcoin_hash(), work)) | ||||
|                 Ok::<_, CompactFiltersError>((header.block_hash(), work)) | ||||
|             }) | ||||
|             .transpose()? | ||||
|             .ok_or(CompactFiltersError::DataCorruption)?; | ||||
| @ -603,13 +602,13 @@ impl<T: StoreType> ChainStore<T> { | ||||
|                 return Err(CompactFiltersError::InvalidHeaders); | ||||
|             } | ||||
| 
 | ||||
|             last_hash = header.bitcoin_hash(); | ||||
|             last_hash = header.block_hash(); | ||||
|             accumulated_work = accumulated_work + header.work(); | ||||
| 
 | ||||
|             let height = from + index + 1; | ||||
|             batch.put_cf( | ||||
|                 cf_handle, | ||||
|                 StoreEntry::BlockHeaderIndex(Some(header.bitcoin_hash())).get_key(), | ||||
|                 StoreEntry::BlockHeaderIndex(Some(header.block_hash())).get_key(), | ||||
|                 &(height).to_be_bytes(), | ||||
|             ); | ||||
|             batch.put_cf( | ||||
| @ -647,8 +646,8 @@ pub struct FilterHeader { | ||||
|     filter_hash: FilterHash, | ||||
| } | ||||
| 
 | ||||
| impl BitcoinHash<FilterHeaderHash> for FilterHeader { | ||||
|     fn bitcoin_hash(&self) -> FilterHeaderHash { | ||||
| impl FilterHeader { | ||||
|     fn header_hash(&self) -> FilterHeaderHash { | ||||
|         let mut hash_data = self.filter_hash.into_inner().to_vec(); | ||||
|         hash_data.extend_from_slice(&self.prev_header_hash); | ||||
|         sha256d::Hash::hash(&hash_data).into() | ||||
| @ -794,7 +793,7 @@ impl CFStore { | ||||
|                     prev_header_hash: last_hash, | ||||
|                     filter_hash, | ||||
|                 }; | ||||
|                 last_hash = filter_header.bitcoin_hash(); | ||||
|                 last_hash = filter_header.header_hash(); | ||||
| 
 | ||||
|                 filter_header | ||||
|             }) | ||||
|  | ||||
| @ -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; | ||||
| use std::convert::{Into, TryFrom}; | ||||
| use std::collections::{BTreeMap, HashMap}; | ||||
| use std::fmt; | ||||
| use std::str::FromStr; | ||||
| use std::sync::Arc; | ||||
| 
 | ||||
| use bitcoin::hashes::{hash160, Hash}; | ||||
| use bitcoin::secp256k1::{All, Secp256k1}; | ||||
| use bitcoin::util::bip32::{DerivationPath, ExtendedPrivKey, Fingerprint}; | ||||
| use bitcoin::util::psbt::PartiallySignedTransaction as PSBT; | ||||
| use bitcoin::{PrivateKey, PublicKey, Script}; | ||||
| use bitcoin::hashes::hash160; | ||||
| use bitcoin::secp256k1::Secp256k1; | ||||
| use bitcoin::util::bip32::{ChildNumber, DerivationPath, Fingerprint}; | ||||
| use bitcoin::util::psbt; | ||||
| use bitcoin::{PublicKey, Script, TxOut}; | ||||
| 
 | ||||
| use miniscript::descriptor::{DescriptorPublicKey, DescriptorXKey, InnerXKey}; | ||||
| pub use miniscript::{ | ||||
|     Descriptor, Legacy, Miniscript, MiniscriptKey, ScriptContext, Segwitv0, Terminal, | ||||
|     Descriptor, Legacy, Miniscript, MiniscriptKey, ScriptContext, Segwitv0, Terminal, ToPublicKey, | ||||
| }; | ||||
| 
 | ||||
| use serde::{Deserialize, Serialize}; | ||||
| 
 | ||||
| use crate::psbt::utils::PSBTUtils; | ||||
| 
 | ||||
| pub mod checksum; | ||||
| pub mod error; | ||||
| pub mod extended_key; | ||||
| mod keys; | ||||
| pub mod policy; | ||||
| 
 | ||||
| // use crate::wallet::utils::AddressType;
 | ||||
| use crate::wallet::signer::SignersContainer; | ||||
| 
 | ||||
| pub use self::checksum::get_checksum; | ||||
| use self::error::Error; | ||||
| pub use self::extended_key::{DerivationIndex, DescriptorExtendedKey}; | ||||
| pub use self::policy::Policy; | ||||
| 
 | ||||
| use self::keys::Key; | ||||
| pub type ExtendedDescriptor = Descriptor<DescriptorPublicKey>; | ||||
| type HDKeyPaths = BTreeMap<PublicKey, (Fingerprint, DerivationPath)>; | ||||
| 
 | ||||
| trait MiniscriptExtractPolicy { | ||||
| pub trait ExtractPolicy { | ||||
|     fn extract_policy( | ||||
|         &self, | ||||
|         lookup_map: &BTreeMap<String, Box<dyn Key>>, | ||||
|         signers: Arc<SignersContainer<DescriptorPublicKey>>, | ||||
|     ) -> Result<Option<Policy>, Error>; | ||||
| } | ||||
| 
 | ||||
| pub trait ExtractPolicy { | ||||
|     fn extract_policy(&self) -> Result<Option<Policy>, Error>; | ||||
| pub trait XKeyUtils { | ||||
|     fn full_path(&self, append: &[ChildNumber]) -> DerivationPath; | ||||
|     fn root_fingerprint(&self) -> Fingerprint; | ||||
| } | ||||
| 
 | ||||
| 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)] | ||||
| @ -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)] | ||||
| mod test { | ||||
|     use std::str::FromStr; | ||||
| 
 | ||||
|     use bitcoin::consensus::encode::deserialize; | ||||
|     use bitcoin::hashes::hex::FromHex; | ||||
|     use bitcoin::{Network, PublicKey}; | ||||
|     use bitcoin::util::psbt; | ||||
| 
 | ||||
|     use crate::descriptor::*; | ||||
| 
 | ||||
|     macro_rules! hex_fingerprint { | ||||
|         ($hex:expr) => { | ||||
|             Fingerprint::from_hex($hex).unwrap() | ||||
|         }; | ||||
|     } | ||||
| 
 | ||||
|     macro_rules! hex_pubkey { | ||||
|         ($hex:expr) => { | ||||
|             PublicKey::from_str($hex).unwrap() | ||||
|         }; | ||||
|     } | ||||
| 
 | ||||
|     macro_rules! deriv_path { | ||||
|         ($str:expr) => { | ||||
|             DerivationPath::from_str($str).unwrap() | ||||
|         }; | ||||
| 
 | ||||
|         () => { | ||||
|             DerivationPath::from(vec![]) | ||||
|         }; | ||||
|     } | ||||
|     use super::*; | ||||
|     use crate::psbt::PSBTUtils; | ||||
| 
 | ||||
|     #[test] | ||||
|     fn test_descriptor_parse_wif() { | ||||
|         let string = "pkh(cVt4o7BGAig1UXywgGSmARhxMdzP5qvQsxKkSsc1XEkw3tDTQFpy)"; | ||||
|         let desc = ExtendedDescriptor::from_str(string).unwrap(); | ||||
|         assert!(desc.is_fixed()); | ||||
|         assert_eq!( | ||||
|             desc.derive(0) | ||||
|                 .unwrap() | ||||
|                 .address(Network::Testnet) | ||||
|                 .unwrap() | ||||
|                 .to_string(), | ||||
|             "mqwpxxvfv3QbM8PU8uBx2jaNt9btQqvQNx" | ||||
|         ); | ||||
|         assert_eq!( | ||||
|             desc.derive(42) | ||||
|                 .unwrap() | ||||
|                 .address(Network::Testnet) | ||||
|                 .unwrap() | ||||
|                 .to_string(), | ||||
|             "mqwpxxvfv3QbM8PU8uBx2jaNt9btQqvQNx" | ||||
|         ); | ||||
|         assert_eq!( | ||||
|             desc.get_secret_keys().into_iter().collect::<Vec<_>>().len(), | ||||
|             1 | ||||
|         ); | ||||
|     } | ||||
|     fn test_derive_from_psbt_input_wpkh() { | ||||
|         let psbt: psbt::PartiallySignedTransaction = deserialize(&Vec::<u8>::from_hex("70736274ff010052010000000162307be8e431fbaff807cdf9cdc3fde44d740211bc8342c31ffd6ec11fe35bcc0100000000ffffffff01328601000000000016001493ce48570b55c42c2af816aeaba06cfee1224fae000000000001011fa08601000000000016001493ce48570b55c42c2af816aeaba06cfee1224fae010304010000000000").unwrap()).unwrap(); | ||||
| 
 | ||||
|     #[test] | ||||
|     fn test_descriptor_parse_pubkey() { | ||||
|         let string = "pkh(039b6347398505f5ec93826dc61c19f47c66c0283ee9be980e29ce325a0f4679ef)"; | ||||
|         let desc = ExtendedDescriptor::from_str(string).unwrap(); | ||||
|         assert!(desc.is_fixed()); | ||||
|         assert_eq!( | ||||
|             desc.derive(0) | ||||
|                 .unwrap() | ||||
|                 .address(Network::Testnet) | ||||
|                 .unwrap() | ||||
|                 .to_string(), | ||||
|             "mqwpxxvfv3QbM8PU8uBx2jaNt9btQqvQNx" | ||||
|         ); | ||||
|         assert_eq!( | ||||
|             desc.derive(42) | ||||
|                 .unwrap() | ||||
|                 .address(Network::Testnet) | ||||
|                 .unwrap() | ||||
|                 .to_string(), | ||||
|             "mqwpxxvfv3QbM8PU8uBx2jaNt9btQqvQNx" | ||||
|         ); | ||||
|         assert_eq!( | ||||
|             desc.get_secret_keys().into_iter().collect::<Vec<_>>().len(), | ||||
|             0 | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     #[test] | ||||
|     fn test_descriptor_parse_xpub() { | ||||
|         let string = "pkh(tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/*)"; | ||||
|         let desc = ExtendedDescriptor::from_str(string).unwrap(); | ||||
|         assert!(!desc.is_fixed()); | ||||
|         assert_eq!( | ||||
|             desc.derive(0) | ||||
|                 .unwrap() | ||||
|                 .address(Network::Testnet) | ||||
|                 .unwrap() | ||||
|                 .to_string(), | ||||
|             "mxbXpnVkwARGtYXk5yeGYf59bGWuPpdE4X" | ||||
|         ); | ||||
|         assert_eq!( | ||||
|             desc.derive(42) | ||||
|                 .unwrap() | ||||
|                 .address(Network::Testnet) | ||||
|                 .unwrap() | ||||
|                 .to_string(), | ||||
|             "mhtuS1QaEV4HPcK4bWk4Wvpd64SUjiC5Zt" | ||||
|         ); | ||||
|         assert_eq!(desc.get_xprv().into_iter().collect::<Vec<_>>().len(), 0); | ||||
|     } | ||||
| 
 | ||||
|     #[test] | ||||
|     #[should_panic(expected = "KeyParsingError")] | ||||
|     fn test_descriptor_parse_fail() { | ||||
|         let string = "pkh(this_is_not_a_valid_key)"; | ||||
|         ExtendedDescriptor::from_str(string).unwrap(); | ||||
|     } | ||||
| 
 | ||||
|     #[test] | ||||
|     fn test_descriptor_hd_keypaths() { | ||||
|         let string = "pkh(tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/*)"; | ||||
|         let desc = ExtendedDescriptor::from_str(string).unwrap(); | ||||
|         let keypaths = desc.get_hd_keypaths(0).unwrap(); | ||||
|         assert!(keypaths.contains_key(&hex_pubkey!( | ||||
|             "025d5fc65ebb8d44a5274b53bac21ff8307fec2334a32df05553459f8b1f7fe1b6" | ||||
|         ))); | ||||
|         assert_eq!( | ||||
|             keypaths.get(&hex_pubkey!( | ||||
|                 "025d5fc65ebb8d44a5274b53bac21ff8307fec2334a32df05553459f8b1f7fe1b6" | ||||
|             )), | ||||
|             Some(&(hex_fingerprint!("31a507b8"), deriv_path!("m/0"))) | ||||
|         let descriptor = Descriptor::<DescriptorPublicKey>::from_str( | ||||
|             "wpkh(02b4632d08485ff1df2db55b9dafd23347d1c47a457072a1e87be26896549a8737)", | ||||
|         ) | ||||
|         .unwrap(); | ||||
| 
 | ||||
|         let result = descriptor.derive_from_psbt_input(&psbt.inputs[0], psbt.get_utxo_for(0)); | ||||
|         println!("{:?}", result); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -1,23 +1,26 @@ | ||||
| use std::cmp::max; | ||||
| use std::collections::{BTreeMap, HashSet, VecDeque}; | ||||
| use std::sync::Arc; | ||||
| 
 | ||||
| use serde::ser::SerializeMap; | ||||
| use serde::{Serialize, Serializer}; | ||||
| 
 | ||||
| use bitcoin::hashes::*; | ||||
| use bitcoin::secp256k1::Secp256k1; | ||||
| use bitcoin::util::bip32::Fingerprint; | ||||
| use bitcoin::PublicKey; | ||||
| 
 | ||||
| use miniscript::{Descriptor, Miniscript, ScriptContext, Terminal}; | ||||
| use miniscript::descriptor::DescriptorPublicKey; | ||||
| use miniscript::{Descriptor, Miniscript, MiniscriptKey, ScriptContext, Terminal}; | ||||
| 
 | ||||
| #[allow(unused_imports)] | ||||
| use log::{debug, error, info, trace}; | ||||
| 
 | ||||
| use crate::descriptor::ExtractPolicy; | ||||
| use crate::wallet::signer::{SignerId, SignersContainer}; | ||||
| 
 | ||||
| use super::checksum::get_checksum; | ||||
| use super::error::Error; | ||||
| use crate::descriptor::{Key, MiniscriptExtractPolicy}; | ||||
| use crate::psbt::PSBTSatisfier; | ||||
| use super::XKeyUtils; | ||||
| 
 | ||||
| #[derive(Debug, Clone, Default, Serialize)] | ||||
| pub struct PKOrF { | ||||
| @ -30,20 +33,23 @@ pub struct PKOrF { | ||||
| } | ||||
| 
 | ||||
| impl PKOrF { | ||||
|     fn from_key(k: &Box<dyn Key>) -> Self { | ||||
|         let secp = Secp256k1::gen_new(); | ||||
|     fn from_key(k: &DescriptorPublicKey) -> Self { | ||||
|         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(); | ||||
|         if let Some(fing) = k.fingerprint(&secp) { | ||||
|             PKOrF { | ||||
|                 fingerprint: Some(fing), | ||||
|                 ..Default::default() | ||||
|             } | ||||
|         } else { | ||||
|             PKOrF { | ||||
|                 pubkey: Some(pubkey), | ||||
|                 ..Default::default() | ||||
|             } | ||||
|     fn from_key_hash(k: hash160::Hash) -> Self { | ||||
|         PKOrF { | ||||
|             pubkey_hash: Some(k), | ||||
|             ..Default::default() | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -445,14 +451,15 @@ impl Policy { | ||||
|     } | ||||
| 
 | ||||
|     fn make_multisig( | ||||
|         keys: Vec<Option<&Box<dyn Key>>>, | ||||
|         keys: &Vec<DescriptorPublicKey>, | ||||
|         signers: Arc<SignersContainer<DescriptorPublicKey>>, | ||||
|         threshold: usize, | ||||
|     ) -> Result<Option<Policy>, PolicyError> { | ||||
|         if threshold == 0 { | ||||
|             return Ok(None); | ||||
|         } | ||||
| 
 | ||||
|         let parsed_keys = keys.iter().map(|k| PKOrF::from_key(k.unwrap())).collect(); | ||||
|         let parsed_keys = keys.iter().map(|k| PKOrF::from_key(k)).collect(); | ||||
| 
 | ||||
|         let mut contribution = Satisfaction::Partial { | ||||
|             n: keys.len(), | ||||
| @ -461,14 +468,14 @@ impl Policy { | ||||
|             conditions: Default::default(), | ||||
|         }; | ||||
|         for (index, key) in keys.iter().enumerate() { | ||||
|             let val = if key.is_some() && key.unwrap().has_secret() { | ||||
|                 Satisfaction::Complete { | ||||
|                     condition: Default::default(), | ||||
|                 } | ||||
|             } else { | ||||
|                 Satisfaction::None | ||||
|             }; | ||||
|             contribution.add(&val, index)?; | ||||
|             if let Some(_) = signers.find(signer_id(key)) { | ||||
|                 contribution.add( | ||||
|                     &Satisfaction::Complete { | ||||
|                         condition: Default::default(), | ||||
|                     }, | ||||
|                     index, | ||||
|                 )?; | ||||
|             } | ||||
|         } | ||||
|         contribution.finalize()?; | ||||
| 
 | ||||
| @ -482,15 +489,6 @@ impl Policy { | ||||
|         Ok(Some(policy)) | ||||
|     } | ||||
| 
 | ||||
|     pub fn satisfy<Ctx: ScriptContext>( | ||||
|         &mut self, | ||||
|         _satisfier: &PSBTSatisfier, | ||||
|         _desc_node: &Terminal<PublicKey, Ctx>, | ||||
|     ) { | ||||
|         //self.satisfaction = self.item.satisfy(satisfier, desc_node);
 | ||||
|         //self.contribution += &self.satisfaction;
 | ||||
|     } | ||||
| 
 | ||||
|     pub fn requires_path(&self) -> bool { | ||||
|         self.get_requirements(&BTreeMap::new()).is_err() | ||||
|     } | ||||
| @ -566,60 +564,55 @@ impl From<SatisfiableItem> for Policy { | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| fn signature_from_string(key: Option<&Box<dyn Key>>) -> Option<Policy> { | ||||
|     key.map(|k| { | ||||
|         let mut policy: Policy = SatisfiableItem::Signature(PKOrF::from_key(k)).into(); | ||||
|         policy.contribution = if k.has_secret() { | ||||
|             Satisfaction::Complete { | ||||
|                 condition: Default::default(), | ||||
|             } | ||||
|         } else { | ||||
|             Satisfaction::None | ||||
|         }; | ||||
| 
 | ||||
|         policy | ||||
|     }) | ||||
| fn signer_id(key: &DescriptorPublicKey) -> SignerId<DescriptorPublicKey> { | ||||
|     match key { | ||||
|         DescriptorPublicKey::PubKey(pubkey) => pubkey.to_pubkeyhash().into(), | ||||
|         DescriptorPublicKey::XPub(xpub) => xpub.root_fingerprint().into(), | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| fn signature_key_from_string(key: Option<&Box<dyn Key>>) -> Option<Policy> { | ||||
|     let secp = Secp256k1::gen_new(); | ||||
| fn signature( | ||||
|     key: &DescriptorPublicKey, | ||||
|     signers: Arc<SignersContainer<DescriptorPublicKey>>, | ||||
| ) -> Policy { | ||||
|     let mut policy: Policy = SatisfiableItem::Signature(PKOrF::from_key(key)).into(); | ||||
| 
 | ||||
|     key.map(|k| { | ||||
|         let pubkey = k.as_public_key(&secp, None).unwrap(); | ||||
|         let mut policy: Policy = if let Some(fing) = k.fingerprint(&secp) { | ||||
|             SatisfiableItem::SignatureKey(PKOrF { | ||||
|                 fingerprint: Some(fing), | ||||
|                 ..Default::default() | ||||
|             }) | ||||
|         } else { | ||||
|             SatisfiableItem::SignatureKey(PKOrF { | ||||
|                 pubkey_hash: Some(hash160::Hash::hash(&pubkey.to_bytes())), | ||||
|                 ..Default::default() | ||||
|             }) | ||||
|     policy.contribution = if signers.find(signer_id(key)).is_some() { | ||||
|         Satisfaction::Complete { | ||||
|             condition: Default::default(), | ||||
|         } | ||||
|         .into(); | ||||
|         policy.contribution = if k.has_secret() { | ||||
|             Satisfaction::Complete { | ||||
|                 condition: Default::default(), | ||||
|             } | ||||
|         } else { | ||||
|             Satisfaction::None | ||||
|         }; | ||||
|     } 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( | ||||
|         &self, | ||||
|         lookup_map: &BTreeMap<String, Box<dyn Key>>, | ||||
|         signers: Arc<SignersContainer<DescriptorPublicKey>>, | ||||
|     ) -> Result<Option<Policy>, Error> { | ||||
|         Ok(match &self.node { | ||||
|             // Leaves
 | ||||
|             Terminal::True | Terminal::False => None, | ||||
|             Terminal::PkK(pubkey) => signature_from_string(lookup_map.get(pubkey)), | ||||
|             Terminal::PkH(pubkey_hash) => signature_key_from_string(lookup_map.get(pubkey_hash)), | ||||
|             Terminal::PkK(pubkey) => Some(signature(pubkey, Arc::clone(&signers))), | ||||
|             Terminal::PkH(pubkey_hash) => Some(signature_key(pubkey_hash, Arc::clone(&signers))), | ||||
|             Terminal::After(value) => { | ||||
|                 let mut policy: Policy = SatisfiableItem::AbsoluteTimelock { value: *value }.into(); | ||||
|                 policy.contribution = Satisfaction::Complete { | ||||
| @ -652,9 +645,7 @@ impl<Ctx: ScriptContext> MiniscriptExtractPolicy for Miniscript<String, Ctx> { | ||||
|             Terminal::Hash160(hash) => { | ||||
|                 Some(SatisfiableItem::HASH160Preimage { hash: *hash }.into()) | ||||
|             } | ||||
|             Terminal::Multi(k, pks) => { | ||||
|                 Policy::make_multisig(pks.iter().map(|s| lookup_map.get(s)).collect(), *k)? | ||||
|             } | ||||
|             Terminal::Multi(k, pks) => Policy::make_multisig(pks, Arc::clone(&signers), *k)?, | ||||
|             // Identities
 | ||||
|             Terminal::Alt(inner) | ||||
|             | Terminal::Swap(inner) | ||||
| @ -662,26 +653,31 @@ impl<Ctx: ScriptContext> MiniscriptExtractPolicy for Miniscript<String, Ctx> { | ||||
|             | Terminal::DupIf(inner) | ||||
|             | Terminal::Verify(inner) | ||||
|             | Terminal::NonZero(inner) | ||||
|             | Terminal::ZeroNotEqual(inner) => inner.extract_policy(lookup_map)?, | ||||
|             | Terminal::ZeroNotEqual(inner) => inner.extract_policy(Arc::clone(&signers))?, | ||||
|             // Complex policies
 | ||||
|             Terminal::AndV(a, b) | Terminal::AndB(a, b) => { | ||||
|                 Policy::make_and(a.extract_policy(lookup_map)?, b.extract_policy(lookup_map)?)? | ||||
|             } | ||||
|             Terminal::AndV(a, b) | Terminal::AndB(a, b) => Policy::make_and( | ||||
|                 a.extract_policy(Arc::clone(&signers))?, | ||||
|                 b.extract_policy(Arc::clone(&signers))?, | ||||
|             )?, | ||||
|             Terminal::AndOr(x, y, z) => Policy::make_or( | ||||
|                 Policy::make_and(x.extract_policy(lookup_map)?, y.extract_policy(lookup_map)?)?, | ||||
|                 z.extract_policy(lookup_map)?, | ||||
|                 Policy::make_and( | ||||
|                     x.extract_policy(Arc::clone(&signers))?, | ||||
|                     y.extract_policy(Arc::clone(&signers))?, | ||||
|                 )?, | ||||
|                 z.extract_policy(Arc::clone(&signers))?, | ||||
|             )?, | ||||
|             Terminal::OrB(a, b) | ||||
|             | Terminal::OrD(a, b) | ||||
|             | Terminal::OrC(a, b) | ||||
|             | Terminal::OrI(a, b) => { | ||||
|                 Policy::make_or(a.extract_policy(lookup_map)?, b.extract_policy(lookup_map)?)? | ||||
|             } | ||||
|             | Terminal::OrI(a, b) => Policy::make_or( | ||||
|                 a.extract_policy(Arc::clone(&signers))?, | ||||
|                 b.extract_policy(Arc::clone(&signers))?, | ||||
|             )?, | ||||
|             Terminal::Thresh(k, nodes) => { | ||||
|                 let mut threshold = *k; | ||||
|                 let mapped: Vec<_> = nodes | ||||
|                     .iter() | ||||
|                     .map(|n| n.extract_policy(lookup_map)) | ||||
|                     .map(|n| n.extract_policy(Arc::clone(&signers))) | ||||
|                     .collect::<Result<Vec<_>, _>>()? | ||||
|                     .into_iter() | ||||
|                     .filter_map(|x| x) | ||||
| @ -700,22 +696,18 @@ impl<Ctx: ScriptContext> MiniscriptExtractPolicy for Miniscript<String, Ctx> { | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl MiniscriptExtractPolicy for Descriptor<String> { | ||||
| impl ExtractPolicy for Descriptor<DescriptorPublicKey> { | ||||
|     fn extract_policy( | ||||
|         &self, | ||||
|         lookup_map: &BTreeMap<String, Box<dyn Key>>, | ||||
|         signers: Arc<SignersContainer<DescriptorPublicKey>>, | ||||
|     ) -> Result<Option<Policy>, Error> { | ||||
|         match self { | ||||
|             Descriptor::Pk(pubkey) | ||||
|             | Descriptor::Pkh(pubkey) | ||||
|             | Descriptor::Wpkh(pubkey) | ||||
|             | Descriptor::ShWpkh(pubkey) => Ok(signature_from_string(lookup_map.get(pubkey))), | ||||
|             Descriptor::Bare(inner) | Descriptor::Sh(inner) => { | ||||
|                 Ok(inner.extract_policy(lookup_map)?) | ||||
|             } | ||||
|             Descriptor::Wsh(inner) | Descriptor::ShWsh(inner) => { | ||||
|                 Ok(inner.extract_policy(lookup_map)?) | ||||
|             } | ||||
|             | Descriptor::ShWpkh(pubkey) => Ok(Some(signature(pubkey, signers))), | ||||
|             Descriptor::Bare(inner) | Descriptor::Sh(inner) => Ok(inner.extract_policy(signers)?), | ||||
|             Descriptor::Wsh(inner) | Descriptor::ShWsh(inner) => Ok(inner.extract_policy(signers)?), | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
							
								
								
									
										13
									
								
								src/error.rs
									
									
									
									
									
								
							
							
						
						
									
										13
									
								
								src/error.rs
									
									
									
									
									
								
							| @ -1,4 +1,4 @@ | ||||
| use bitcoin::{Address, OutPoint, Script, Txid}; | ||||
| use bitcoin::{Address, OutPoint}; | ||||
| 
 | ||||
| #[derive(Debug)] | ||||
| pub enum Error { | ||||
| @ -25,13 +25,7 @@ pub enum Error { | ||||
|     SpendingPolicyRequired, | ||||
|     InvalidPolicyPathError(crate::descriptor::policy::PolicyError), | ||||
| 
 | ||||
|     // Signing errors (expected, received)
 | ||||
|     InputTxidMismatch((Txid, OutPoint)), | ||||
|     InputRedeemScriptMismatch((Script, Script)), // scriptPubKey, redeemScript
 | ||||
|     InputWitnessScriptMismatch((Script, Script)), // scriptPubKey, redeemScript
 | ||||
|     InputUnknownSegwitScript(Script), | ||||
|     InputMissingWitnessScript(usize), | ||||
|     MissingUTXO, | ||||
|     Signer(crate::wallet::signer::SignerError), | ||||
| 
 | ||||
|     // Blockchain interface errors
 | ||||
|     Uncapable(crate::blockchain::Capability), | ||||
| @ -44,6 +38,7 @@ pub enum Error { | ||||
|     Descriptor(crate::descriptor::error::Error), | ||||
| 
 | ||||
|     Encode(bitcoin::consensus::encode::Error), | ||||
|     Miniscript(miniscript::Error), | ||||
|     BIP32(bitcoin::util::bip32::Error), | ||||
|     Secp256k1(bitcoin::secp256k1::Error), | ||||
|     JSON(serde_json::Error), | ||||
| @ -75,8 +70,10 @@ impl_error!( | ||||
|     crate::descriptor::policy::PolicyError, | ||||
|     InvalidPolicyPathError | ||||
| ); | ||||
| impl_error!(crate::wallet::signer::SignerError, Signer); | ||||
| 
 | ||||
| impl_error!(bitcoin::consensus::encode::Error, Encode); | ||||
| impl_error!(miniscript::Error, Miniscript); | ||||
| impl_error!(bitcoin::util::bip32::Error, BIP32); | ||||
| impl_error!(bitcoin::secp256k1::Error, Secp256k1); | ||||
| impl_error!(serde_json::Error, JSON); | ||||
|  | ||||
| @ -47,7 +47,6 @@ pub mod blockchain; | ||||
| pub mod database; | ||||
| pub mod descriptor; | ||||
| pub mod psbt; | ||||
| pub mod signer; | ||||
| pub mod types; | ||||
| pub mod wallet; | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										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}; | ||||
| use bitcoin::util::bip143::SighashComponents; | ||||
| use bitcoin::util::bip32::{DerivationPath, ExtendedPrivKey, Fingerprint}; | ||||
| use bitcoin::util::psbt; | ||||
| use bitcoin::{PrivateKey, PublicKey, Script, SigHashType, Transaction}; | ||||
| 
 | ||||
| use bitcoin::secp256k1::{self, All, Message, Secp256k1}; | ||||
| 
 | ||||
| #[allow(unused_imports)] | ||||
| use log::{debug, error, info, trace}; | ||||
| 
 | ||||
| use miniscript::{BitcoinSig, MiniscriptKey, Satisfier}; | ||||
| 
 | ||||
| use crate::descriptor::ExtendedDescriptor; | ||||
| use crate::error::Error; | ||||
| use crate::signer::Signer; | ||||
| 
 | ||||
| pub mod utils; | ||||
| 
 | ||||
| pub struct PSBTSatisfier<'a> { | ||||
|     input: &'a psbt::Input, | ||||
|     assume_height_reached: bool, | ||||
|     create_height: Option<u32>, | ||||
|     current_height: Option<u32>, | ||||
| pub trait PSBTUtils { | ||||
|     fn get_utxo_for(&self, input_index: usize) -> Option<TxOut>; | ||||
| } | ||||
| 
 | ||||
| impl<'a> PSBTSatisfier<'a> { | ||||
|     pub fn new( | ||||
|         input: &'a psbt::Input, | ||||
|         assume_height_reached: bool, | ||||
|         create_height: Option<u32>, | ||||
|         current_height: Option<u32>, | ||||
|     ) -> Self { | ||||
|         PSBTSatisfier { | ||||
|             input, | ||||
|             assume_height_reached, | ||||
|             create_height, | ||||
|             current_height, | ||||
| 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; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl<'a> PSBTSatisfier<'a> { | ||||
|     fn parse_sig(rawsig: &Vec<u8>) -> Option<BitcoinSig> { | ||||
|         let (flag, sig) = rawsig.split_last().unwrap(); | ||||
|         let flag = bitcoin::SigHashType::from_u32(*flag as u32); | ||||
|         let sig = match secp256k1::Signature::from_der(sig) { | ||||
|             Ok(sig) => sig, | ||||
|             Err(..) => return None, | ||||
|         }; | ||||
|         Some((sig, flag)) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| // TODO: also support hash preimages through the "unknown" section of PSBT
 | ||||
| impl<'a> Satisfier<bitcoin::PublicKey> for PSBTSatisfier<'a> { | ||||
|     // from https://docs.rs/miniscript/0.12.0/src/miniscript/psbt/mod.rs.html#96
 | ||||
|     fn lookup_sig(&self, pk: &bitcoin::PublicKey) -> Option<BitcoinSig> { | ||||
|         debug!("lookup_sig: {}", pk); | ||||
| 
 | ||||
|         if let Some(rawsig) = self.input.partial_sigs.get(pk) { | ||||
|             Self::parse_sig(&rawsig) | ||||
|         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 | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     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, | ||||
|         include_blockheight: bool, | ||||
|     ) -> Result<Self, &'static str> { | ||||
|         let descriptor = wallet.descriptor.as_ref().to_string(); | ||||
|         let descriptor = wallet | ||||
|             .descriptor | ||||
|             .to_string_with_secret(&wallet.signers.as_key_map()); | ||||
|         Self::is_compatible_with_core(&descriptor)?; | ||||
| 
 | ||||
|         let blockheight = match wallet.database.borrow().iter_txs(false) { | ||||
| @ -62,7 +64,7 @@ impl WalletExport { | ||||
|             != wallet | ||||
|                 .change_descriptor | ||||
|                 .as_ref() | ||||
|                 .map(|d| d.as_ref().to_string()) | ||||
|                 .map(|d| d.to_string_with_secret(&wallet.change_signers.as_key_map())) | ||||
|         { | ||||
|             return Err("Incompatible change descriptor"); | ||||
|         } | ||||
| @ -193,14 +195,14 @@ mod test { | ||||
|     #[test] | ||||
|     fn test_export_multi() { | ||||
|         let descriptor = "wsh(multi(2,\ | ||||
|                                 [73756c7f/48h/0h/0h/2h]tpubDCKxNyM3bLgbEX13Mcd8mYxbVg9ajDkWXMh29hMWBurKfVmBfWAM96QVP3zaUcN51HvkZ3ar4VwP82kC8JZhhux8vFQoJintSpVBwpFvyU3/0/*,\
 | ||||
|                                 [f9f62194/48h/0h/0h/2h]tpubDDp3ZSH1yCwusRppH7zgSxq2t1VEUyXSeEp8E5aFS8m43MknUjiF1bSLo3CGWAxbDyhF1XowA5ukPzyJZjznYk3kYi6oe7QxtX2euvKWsk4/0/*,\
 | ||||
|                                 [c98b1535/48h/0h/0h/2h]tpubDCDi5W4sP6zSnzJeowy8rQDVhBdRARaPhK1axABi8V1661wEPeanpEXj4ZLAUEoikVtoWcyK26TKKJSecSfeKxwHCcRrge9k1ybuiL71z4a/0/*\
 | ||||
|                                 [73756c7f/48'/0'/0'/2']tpubDCKxNyM3bLgbEX13Mcd8mYxbVg9ajDkWXMh29hMWBurKfVmBfWAM96QVP3zaUcN51HvkZ3ar4VwP82kC8JZhhux8vFQoJintSpVBwpFvyU3/0/*,\
 | ||||
|                                 [f9f62194/48'/0'/0'/2']tpubDDp3ZSH1yCwusRppH7zgSxq2t1VEUyXSeEp8E5aFS8m43MknUjiF1bSLo3CGWAxbDyhF1XowA5ukPzyJZjznYk3kYi6oe7QxtX2euvKWsk4/0/*,\
 | ||||
|                                 [c98b1535/48'/0'/0'/2']tpubDCDi5W4sP6zSnzJeowy8rQDVhBdRARaPhK1axABi8V1661wEPeanpEXj4ZLAUEoikVtoWcyK26TKKJSecSfeKxwHCcRrge9k1ybuiL71z4a/0/*\
 | ||||
|                           ))";
 | ||||
|         let change_descriptor = "wsh(multi(2,\ | ||||
|                                        [73756c7f/48h/0h/0h/2h]tpubDCKxNyM3bLgbEX13Mcd8mYxbVg9ajDkWXMh29hMWBurKfVmBfWAM96QVP3zaUcN51HvkZ3ar4VwP82kC8JZhhux8vFQoJintSpVBwpFvyU3/1/*,\
 | ||||
|                                        [f9f62194/48h/0h/0h/2h]tpubDDp3ZSH1yCwusRppH7zgSxq2t1VEUyXSeEp8E5aFS8m43MknUjiF1bSLo3CGWAxbDyhF1XowA5ukPzyJZjznYk3kYi6oe7QxtX2euvKWsk4/1/*,\
 | ||||
|                                        [c98b1535/48h/0h/0h/2h]tpubDCDi5W4sP6zSnzJeowy8rQDVhBdRARaPhK1axABi8V1661wEPeanpEXj4ZLAUEoikVtoWcyK26TKKJSecSfeKxwHCcRrge9k1ybuiL71z4a/1/*\
 | ||||
|                                        [73756c7f/48'/0'/0'/2']tpubDCKxNyM3bLgbEX13Mcd8mYxbVg9ajDkWXMh29hMWBurKfVmBfWAM96QVP3zaUcN51HvkZ3ar4VwP82kC8JZhhux8vFQoJintSpVBwpFvyU3/1/*,\
 | ||||
|                                        [f9f62194/48'/0'/0'/2']tpubDDp3ZSH1yCwusRppH7zgSxq2t1VEUyXSeEp8E5aFS8m43MknUjiF1bSLo3CGWAxbDyhF1XowA5ukPzyJZjznYk3kYi6oe7QxtX2euvKWsk4/1/*,\
 | ||||
|                                        [c98b1535/48'/0'/0'/2']tpubDCDi5W4sP6zSnzJeowy8rQDVhBdRARaPhK1axABi8V1661wEPeanpEXj4ZLAUEoikVtoWcyK26TKKJSecSfeKxwHCcRrge9k1ybuiL71z4a/1/*\
 | ||||
|                                  ))";
 | ||||
| 
 | ||||
|         let wallet: OfflineWallet<_> = Wallet::new_offline( | ||||
|  | ||||
| @ -2,17 +2,14 @@ use std::cell::RefCell; | ||||
| use std::collections::HashMap; | ||||
| use std::collections::{BTreeMap, HashSet}; | ||||
| use std::ops::{Deref, DerefMut}; | ||||
| use std::str::FromStr; | ||||
| use std::sync::Arc; | ||||
| 
 | ||||
| use bitcoin::blockdata::opcodes; | ||||
| use bitcoin::blockdata::script::Builder; | ||||
| use bitcoin::consensus::encode::serialize; | ||||
| use bitcoin::util::bip32::ChildNumber; | ||||
| use bitcoin::util::psbt::PartiallySignedTransaction as PSBT; | ||||
| use bitcoin::{ | ||||
|     Address, Network, OutPoint, PublicKey, Script, SigHashType, Transaction, TxOut, Txid, | ||||
| }; | ||||
| use bitcoin::{Address, Network, OutPoint, Script, SigHashType, Transaction, TxOut, Txid}; | ||||
| 
 | ||||
| use miniscript::BitcoinSig; | ||||
| use miniscript::descriptor::DescriptorPublicKey; | ||||
| 
 | ||||
| #[allow(unused_imports)] | ||||
| use log::{debug, error, info, trace}; | ||||
| @ -20,19 +17,23 @@ use log::{debug, error, info, trace}; | ||||
| pub mod coin_selection; | ||||
| pub mod export; | ||||
| mod rbf; | ||||
| pub mod signer; | ||||
| pub mod time; | ||||
| pub mod tx_builder; | ||||
| pub mod utils; | ||||
| 
 | ||||
| use signer::{Signer, SignersContainer}; | ||||
| use tx_builder::TxBuilder; | ||||
| use utils::{FeeRate, IsDust}; | ||||
| use utils::{After, FeeRate, IsDust, Older}; | ||||
| 
 | ||||
| use crate::blockchain::{Blockchain, OfflineBlockchain, OnlineBlockchain, Progress}; | ||||
| use crate::database::{BatchDatabase, BatchOperations, DatabaseUtils}; | ||||
| use crate::descriptor::{get_checksum, DescriptorMeta, ExtendedDescriptor, ExtractPolicy, Policy}; | ||||
| use crate::descriptor::{ | ||||
|     get_checksum, DescriptorMeta, DescriptorScripts, ExtendedDescriptor, ExtractPolicy, Policy, | ||||
| }; | ||||
| use crate::error::Error; | ||||
| use crate::psbt::{utils::PSBTUtils, PSBTSatisfier, PSBTSigner}; | ||||
| use crate::signer::Signer; | ||||
| use crate::psbt::PSBTUtils; | ||||
| // use crate::psbt::{utils::PSBTUtils, PSBTSatisfier, PSBTSigner};
 | ||||
| use crate::types::*; | ||||
| 
 | ||||
| const CACHE_ADDR_BATCH_SIZE: u32 = 100; | ||||
| @ -42,6 +43,10 @@ pub type OfflineWallet<D> = Wallet<OfflineBlockchain, D>; | ||||
| pub struct Wallet<B: Blockchain, D: BatchDatabase> { | ||||
|     descriptor: ExtendedDescriptor, | ||||
|     change_descriptor: Option<ExtendedDescriptor>, | ||||
| 
 | ||||
|     signers: Arc<SignersContainer<DescriptorPublicKey>>, | ||||
|     change_signers: Arc<SignersContainer<DescriptorPublicKey>>, | ||||
| 
 | ||||
|     network: Network, | ||||
| 
 | ||||
|     current_height: Option<u32>, | ||||
| @ -66,27 +71,32 @@ where | ||||
|             ScriptType::External, | ||||
|             get_checksum(descriptor)?.as_bytes(), | ||||
|         )?; | ||||
|         let descriptor = ExtendedDescriptor::from_str(descriptor)?; | ||||
|         let change_descriptor = match change_descriptor { | ||||
|         let (descriptor, keymap) = ExtendedDescriptor::parse_secret(descriptor)?; | ||||
|         let signers = Arc::new(SignersContainer::from(keymap)); | ||||
|         let (change_descriptor, change_signers) = match change_descriptor { | ||||
|             Some(desc) => { | ||||
|                 database.check_descriptor_checksum( | ||||
|                     ScriptType::Internal, | ||||
|                     get_checksum(desc)?.as_bytes(), | ||||
|                 )?; | ||||
| 
 | ||||
|                 let parsed = ExtendedDescriptor::from_str(desc)?; | ||||
|                 if !parsed.same_structure(descriptor.as_ref()) { | ||||
|                     return Err(Error::DifferentDescriptorStructure); | ||||
|                 } | ||||
|                 let (change_descriptor, change_keymap) = ExtendedDescriptor::parse_secret(desc)?; | ||||
|                 let change_signers = Arc::new(SignersContainer::from(change_keymap)); | ||||
|                 // if !parsed.same_structure(descriptor.as_ref()) {
 | ||||
|                 //     return Err(Error::DifferentDescriptorStructure);
 | ||||
|                 // }
 | ||||
| 
 | ||||
|                 Some(parsed) | ||||
|                 (Some(change_descriptor), change_signers) | ||||
|             } | ||||
|             None => None, | ||||
|             None => (None, Arc::new(SignersContainer::new())), | ||||
|         }; | ||||
| 
 | ||||
|         Ok(Wallet { | ||||
|             descriptor, | ||||
|             change_descriptor, | ||||
|             signers, | ||||
|             change_signers, | ||||
| 
 | ||||
|             network, | ||||
| 
 | ||||
|             current_height: None, | ||||
| @ -100,7 +110,7 @@ where | ||||
|         let index = self.fetch_and_increment_index(ScriptType::External)?; | ||||
| 
 | ||||
|         self.descriptor | ||||
|             .derive(index)? | ||||
|             .derive(&[ChildNumber::from_normal_idx(index).unwrap()]) | ||||
|             .address(self.network) | ||||
|             .ok_or(Error::ScriptDoesntHaveAddressForm) | ||||
|     } | ||||
| @ -133,7 +143,10 @@ where | ||||
|         } | ||||
| 
 | ||||
|         // TODO: fetch both internal and external policies
 | ||||
|         let policy = self.descriptor.extract_policy()?.unwrap(); | ||||
|         let policy = self | ||||
|             .descriptor | ||||
|             .extract_policy(Arc::clone(&self.signers))? | ||||
|             .unwrap(); | ||||
|         if policy.requires_path() && builder.policy_path.is_none() { | ||||
|             return Err(Error::SpendingPolicyRequired); | ||||
|         } | ||||
| @ -220,10 +233,10 @@ where | ||||
|         // TODO: use the right weight instead of the maximum, and only fall-back to it if the
 | ||||
|         // script is unknown in the database
 | ||||
|         let input_witness_weight = std::cmp::max( | ||||
|             self.get_descriptor_for(ScriptType::Internal) | ||||
|             self.get_descriptor_for_script_type(ScriptType::Internal) | ||||
|                 .0 | ||||
|                 .max_satisfaction_weight(), | ||||
|             self.get_descriptor_for(ScriptType::External) | ||||
|             self.get_descriptor_for_script_type(ScriptType::External) | ||||
|                 .0 | ||||
|                 .max_satisfaction_weight(), | ||||
|         ); | ||||
| @ -369,7 +382,7 @@ where | ||||
|                 // `get_deget_descriptor_for` to find what's the ScriptType for `Internal`
 | ||||
|                 // addresses really is, because if there's no change_descriptor it's actually equal
 | ||||
|                 // to "External"
 | ||||
|                 let (_, change_type) = self.get_descriptor_for(ScriptType::Internal); | ||||
|                 let (_, change_type) = self.get_descriptor_for_script_type(ScriptType::Internal); | ||||
|                 match self | ||||
|                     .database | ||||
|                     .borrow() | ||||
| @ -435,10 +448,10 @@ where | ||||
|                     // TODO: use the right weight instead of the maximum, and only fall-back to it if the
 | ||||
|                     // script is unknown in the database
 | ||||
|                     let input_witness_weight = std::cmp::max( | ||||
|                         self.get_descriptor_for(ScriptType::Internal) | ||||
|                         self.get_descriptor_for_script_type(ScriptType::Internal) | ||||
|                             .0 | ||||
|                             .max_satisfaction_weight(), | ||||
|                         self.get_descriptor_for(ScriptType::External) | ||||
|                         self.get_descriptor_for_script_type(ScriptType::External) | ||||
|                             .0 | ||||
|                             .max_satisfaction_weight(), | ||||
|                     ); | ||||
| @ -545,139 +558,11 @@ where | ||||
|         // this helps us doing our job later
 | ||||
|         self.add_input_hd_keypaths(&mut psbt)?; | ||||
| 
 | ||||
|         let tx = &psbt.global.unsigned_tx; | ||||
| 
 | ||||
|         let mut signer = PSBTSigner::from_descriptor(&psbt.global.unsigned_tx, &self.descriptor)?; | ||||
|         if let Some(desc) = &self.change_descriptor { | ||||
|             let change_signer = PSBTSigner::from_descriptor(&psbt.global.unsigned_tx, desc)?; | ||||
|             signer.extend(change_signer)?; | ||||
|         } | ||||
| 
 | ||||
|         // sign everything we can. TODO: ideally we should only sign with the keys in the policy
 | ||||
|         // path selected, if present
 | ||||
|         for (i, input) in psbt.inputs.iter_mut().enumerate() { | ||||
|             let sighash = input.sighash_type.unwrap_or(SigHashType::All); | ||||
|             let prevout = tx.input[i].previous_output; | ||||
| 
 | ||||
|             let mut partial_sigs = BTreeMap::new(); | ||||
|             { | ||||
|                 let mut push_sig = |pubkey: &PublicKey, opt_sig: Option<BitcoinSig>| { | ||||
|                     if let Some((signature, sighash)) = opt_sig { | ||||
|                         let mut concat_sig = Vec::new(); | ||||
|                         concat_sig.extend_from_slice(&signature.serialize_der()); | ||||
|                         concat_sig.extend_from_slice(&[sighash as u8]); | ||||
|                         //input.partial_sigs.insert(*pubkey, concat_sig);
 | ||||
|                         partial_sigs.insert(*pubkey, concat_sig); | ||||
|                     } | ||||
|                 }; | ||||
| 
 | ||||
|                 if let Some(non_wit_utxo) = &input.non_witness_utxo { | ||||
|                     if non_wit_utxo.txid() != prevout.txid { | ||||
|                         return Err(Error::InputTxidMismatch((non_wit_utxo.txid(), prevout))); | ||||
|                     } | ||||
| 
 | ||||
|                     let prev_script = &non_wit_utxo.output | ||||
|                         [psbt.global.unsigned_tx.input[i].previous_output.vout as usize] | ||||
|                         .script_pubkey; | ||||
| 
 | ||||
|                     // return (signature, sighash) from here
 | ||||
|                     let sign_script = if let Some(redeem_script) = &input.redeem_script { | ||||
|                         if &redeem_script.to_p2sh() != prev_script { | ||||
|                             return Err(Error::InputRedeemScriptMismatch(( | ||||
|                                 prev_script.clone(), | ||||
|                                 redeem_script.clone(), | ||||
|                             ))); | ||||
|                         } | ||||
| 
 | ||||
|                         redeem_script | ||||
|                     } else { | ||||
|                         prev_script | ||||
|                     }; | ||||
| 
 | ||||
|                     for (pubkey, (fing, path)) in &input.hd_keypaths { | ||||
|                         push_sig( | ||||
|                             pubkey, | ||||
|                             signer.sig_legacy_from_fingerprint( | ||||
|                                 i, | ||||
|                                 sighash, | ||||
|                                 fing, | ||||
|                                 path, | ||||
|                                 sign_script, | ||||
|                             )?, | ||||
|                         ); | ||||
|                     } | ||||
|                     // TODO: this sucks, we sign with every key
 | ||||
|                     for pubkey in signer.all_public_keys() { | ||||
|                         push_sig( | ||||
|                             pubkey, | ||||
|                             signer.sig_legacy_from_pubkey(i, sighash, pubkey, sign_script)?, | ||||
|                         ); | ||||
|                     } | ||||
|                 } else if let Some(witness_utxo) = &input.witness_utxo { | ||||
|                     let value = witness_utxo.value; | ||||
| 
 | ||||
|                     let script = match &input.redeem_script { | ||||
|                         Some(script) if script.to_p2sh() != witness_utxo.script_pubkey => { | ||||
|                             return Err(Error::InputRedeemScriptMismatch(( | ||||
|                                 witness_utxo.script_pubkey.clone(), | ||||
|                                 script.clone(), | ||||
|                             ))) | ||||
|                         } | ||||
|                         Some(script) => script, | ||||
|                         None => &witness_utxo.script_pubkey, | ||||
|                     }; | ||||
| 
 | ||||
|                     let sign_script = if script.is_v0_p2wpkh() { | ||||
|                         self.to_p2pkh(&script.as_bytes()[2..]) | ||||
|                     } else if script.is_v0_p2wsh() { | ||||
|                         match &input.witness_script { | ||||
|                             None => Err(Error::InputMissingWitnessScript(i)), | ||||
|                             Some(witness_script) if script != &witness_script.to_v0_p2wsh() => { | ||||
|                                 Err(Error::InputRedeemScriptMismatch(( | ||||
|                                     script.clone(), | ||||
|                                     witness_script.clone(), | ||||
|                                 ))) | ||||
|                             } | ||||
|                             Some(witness_script) => Ok(witness_script), | ||||
|                         }? | ||||
|                         .clone() | ||||
|                     } else { | ||||
|                         return Err(Error::InputUnknownSegwitScript(script.clone())); | ||||
|                     }; | ||||
| 
 | ||||
|                     for (pubkey, (fing, path)) in &input.hd_keypaths { | ||||
|                         push_sig( | ||||
|                             pubkey, | ||||
|                             signer.sig_segwit_from_fingerprint( | ||||
|                                 i, | ||||
|                                 sighash, | ||||
|                                 fing, | ||||
|                                 path, | ||||
|                                 &sign_script, | ||||
|                                 value, | ||||
|                             )?, | ||||
|                         ); | ||||
|                     } | ||||
|                     // TODO: this sucks, we sign with every key
 | ||||
|                     for pubkey in signer.all_public_keys() { | ||||
|                         push_sig( | ||||
|                             pubkey, | ||||
|                             signer.sig_segwit_from_pubkey( | ||||
|                                 i, | ||||
|                                 sighash, | ||||
|                                 pubkey, | ||||
|                                 &sign_script, | ||||
|                                 value, | ||||
|                             )?, | ||||
|                         ); | ||||
|                     } | ||||
|                 } else { | ||||
|                     return Err(Error::MissingUTXO); | ||||
|                 } | ||||
|         for index in 0..psbt.inputs.len() { | ||||
|             self.signers.sign(&mut psbt, index)?; | ||||
|             if self.change_descriptor.is_some() { | ||||
|                 self.change_signers.sign(&mut psbt, index)?; | ||||
|             } | ||||
| 
 | ||||
|             // push all the signatures into the psbt
 | ||||
|             input.partial_sigs.append(&mut partial_sigs); | ||||
|         } | ||||
| 
 | ||||
|         // attempt to finalize
 | ||||
| @ -688,9 +573,13 @@ where | ||||
| 
 | ||||
|     pub fn policies(&self, script_type: ScriptType) -> Result<Option<Policy>, Error> { | ||||
|         match (script_type, self.change_descriptor.as_ref()) { | ||||
|             (ScriptType::External, _) => Ok(self.descriptor.extract_policy()?), | ||||
|             (ScriptType::External, _) => { | ||||
|                 Ok(self.descriptor.extract_policy(Arc::clone(&self.signers))?) | ||||
|             } | ||||
|             (ScriptType::Internal, None) => Ok(None), | ||||
|             (ScriptType::Internal, Some(desc)) => Ok(desc.extract_policy()?), | ||||
|             (ScriptType::Internal, Some(desc)) => { | ||||
|                 Ok(desc.extract_policy(Arc::clone(&self.change_signers))?) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| @ -699,9 +588,9 @@ where | ||||
|         script_type: ScriptType, | ||||
|     ) -> Result<Option<ExtendedDescriptor>, Error> { | ||||
|         match (script_type, self.change_descriptor.as_ref()) { | ||||
|             (ScriptType::External, _) => Ok(Some(self.descriptor.as_public_version()?)), | ||||
|             (ScriptType::External, _) => Ok(Some(self.descriptor.clone())), | ||||
|             (ScriptType::Internal, None) => Ok(None), | ||||
|             (ScriptType::Internal, Some(desc)) => Ok(Some(desc.as_public_version()?)), | ||||
|             (ScriptType::Internal, Some(desc)) => Ok(Some(desc.clone())), | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| @ -712,18 +601,7 @@ where | ||||
|     ) -> Result<bool, Error> { | ||||
|         let mut tx = psbt.global.unsigned_tx.clone(); | ||||
| 
 | ||||
|         for (n, input) in tx.input.iter_mut().enumerate() { | ||||
|             // safe to run only on the descriptor because we assume the change descriptor also has
 | ||||
|             // the same structure
 | ||||
|             let desc = self.descriptor.derive_from_psbt_input(psbt, n); | ||||
|             debug!("{:?}", psbt.inputs[n].hd_keypaths); | ||||
|             debug!("reconstructed descriptor is {:?}", desc); | ||||
| 
 | ||||
|             let desc = match desc { | ||||
|                 Err(_) => return Ok(false), | ||||
|                 Ok(desc) => desc, | ||||
|             }; | ||||
| 
 | ||||
|         for (n, (input, psbt_input)) in tx.input.iter_mut().zip(psbt.inputs.iter()).enumerate() { | ||||
|             // if the height is None in the database it means it's still unconfirmed, so consider
 | ||||
|             // that as a very high value
 | ||||
|             let create_height = self | ||||
| @ -738,10 +616,43 @@ where | ||||
|                 n, input.previous_output, create_height, current_height | ||||
|             ); | ||||
| 
 | ||||
|             let satisfier = | ||||
|                 PSBTSatisfier::new(&psbt.inputs[n], false, create_height, current_height); | ||||
|             // - Try to derive the descriptor by looking at the txout. If it's in our database, we
 | ||||
|             //   know exactly which `script_type` to use, and which derivation index it is
 | ||||
|             // - If that fails, try to derive it by looking at the psbt input: the complete logic
 | ||||
|             //   is in `src/descriptor/mod.rs`, but it will basically look at `hd_keypaths`,
 | ||||
|             //   `redeem_script` and `witness_script` to determine the right derivation
 | ||||
|             // - If that also fails, it will try it on the internal descriptor, if present
 | ||||
|             let desc = if let Some(desc) = psbt | ||||
|                 .get_utxo_for(n) | ||||
|                 .map(|txout| self.get_descriptor_for_txout(&txout)) | ||||
|                 .transpose()? | ||||
|                 .flatten() | ||||
|             { | ||||
|                 desc | ||||
|             } else if let Some(desc) = self | ||||
|                 .descriptor | ||||
|                 .derive_from_psbt_input(psbt_input, psbt.get_utxo_for(n)) | ||||
|             { | ||||
|                 desc | ||||
|             } else if let Some(desc) = self | ||||
|                 .change_descriptor | ||||
|                 .as_ref() | ||||
|                 .and_then(|desc| desc.derive_from_psbt_input(psbt_input, psbt.get_utxo_for(n))) | ||||
|             { | ||||
|                 desc | ||||
|             } else { | ||||
|                 debug!("Couldn't find the right derived descriptor for input {}", n); | ||||
|                 return Ok(false); | ||||
|             }; | ||||
| 
 | ||||
|             match desc.satisfy(input, satisfier) { | ||||
|             match desc.satisfy( | ||||
|                 input, | ||||
|                 ( | ||||
|                     psbt_input.clone(), | ||||
|                     After::new(current_height, false), | ||||
|                     Older::new(current_height, create_height, false), | ||||
|                 ), | ||||
|             ) { | ||||
|                 Ok(_) => continue, | ||||
|                 Err(e) => { | ||||
|                     debug!("satisfy error {:?} for input {}", e, n); | ||||
| @ -761,7 +672,10 @@ where | ||||
| 
 | ||||
|     // Internals
 | ||||
| 
 | ||||
|     fn get_descriptor_for(&self, script_type: ScriptType) -> (&ExtendedDescriptor, ScriptType) { | ||||
|     fn get_descriptor_for_script_type( | ||||
|         &self, | ||||
|         script_type: ScriptType, | ||||
|     ) -> (&ExtendedDescriptor, ScriptType) { | ||||
|         let desc = match script_type { | ||||
|             ScriptType::Internal if self.change_descriptor.is_some() => ( | ||||
|                 self.change_descriptor.as_ref().unwrap(), | ||||
| @ -773,25 +687,26 @@ where | ||||
|         desc | ||||
|     } | ||||
| 
 | ||||
|     fn to_p2pkh(&self, pubkey_hash: &[u8]) -> Script { | ||||
|         Builder::new() | ||||
|             .push_opcode(opcodes::all::OP_DUP) | ||||
|             .push_opcode(opcodes::all::OP_HASH160) | ||||
|             .push_slice(pubkey_hash) | ||||
|             .push_opcode(opcodes::all::OP_EQUALVERIFY) | ||||
|             .push_opcode(opcodes::all::OP_CHECKSIG) | ||||
|             .into_script() | ||||
|     fn get_descriptor_for_txout(&self, txout: &TxOut) -> Result<Option<ExtendedDescriptor>, Error> { | ||||
|         Ok(self | ||||
|             .database | ||||
|             .borrow() | ||||
|             .get_path_from_script_pubkey(&txout.script_pubkey)? | ||||
|             .map(|(script_type, child)| (self.get_descriptor_for_script_type(script_type).0, child)) | ||||
|             .map(|(desc, child)| desc.derive(&[ChildNumber::from_normal_idx(child).unwrap()]))) | ||||
|     } | ||||
| 
 | ||||
|     fn get_change_address(&self) -> Result<Script, Error> { | ||||
|         let (desc, script_type) = self.get_descriptor_for(ScriptType::Internal); | ||||
|         let (desc, script_type) = self.get_descriptor_for_script_type(ScriptType::Internal); | ||||
|         let index = self.fetch_and_increment_index(script_type)?; | ||||
| 
 | ||||
|         Ok(desc.derive(index)?.script_pubkey()) | ||||
|         Ok(desc | ||||
|             .derive(&[ChildNumber::from_normal_idx(index).unwrap()]) | ||||
|             .script_pubkey()) | ||||
|     } | ||||
| 
 | ||||
|     fn fetch_and_increment_index(&self, script_type: ScriptType) -> Result<u32, Error> { | ||||
|         let (descriptor, script_type) = self.get_descriptor_for(script_type); | ||||
|         let (descriptor, script_type) = self.get_descriptor_for_script_type(script_type); | ||||
|         let index = match descriptor.is_fixed() { | ||||
|             true => 0, | ||||
|             false => self | ||||
| @ -818,7 +733,7 @@ where | ||||
|         from: u32, | ||||
|         mut count: u32, | ||||
|     ) -> Result<(), Error> { | ||||
|         let (descriptor, script_type) = self.get_descriptor_for(script_type); | ||||
|         let (descriptor, script_type) = self.get_descriptor_for_script_type(script_type); | ||||
|         if descriptor.is_fixed() { | ||||
|             if from > 0 { | ||||
|                 return Ok(()); | ||||
| @ -832,7 +747,9 @@ where | ||||
|         let start_time = time::Instant::new(); | ||||
|         for i in from..(from + count) { | ||||
|             address_batch.set_script_pubkey( | ||||
|                 &descriptor.derive(i)?.script_pubkey(), | ||||
|                 &descriptor | ||||
|                     .derive(&[ChildNumber::from_normal_idx(i).unwrap()]) | ||||
|                     .script_pubkey(), | ||||
|                 script_type, | ||||
|                 i, | ||||
|             )?; | ||||
| @ -924,9 +841,9 @@ where | ||||
|                 None => continue, | ||||
|             }; | ||||
| 
 | ||||
|             let (desc, _) = self.get_descriptor_for(script_type); | ||||
|             let (desc, _) = self.get_descriptor_for_script_type(script_type); | ||||
|             psbt_input.hd_keypaths = desc.get_hd_keypaths(child)?; | ||||
|             let derived_descriptor = desc.derive(child)?; | ||||
|             let derived_descriptor = desc.derive(&[ChildNumber::from_normal_idx(child).unwrap()]); | ||||
| 
 | ||||
|             psbt_input.redeem_script = derived_descriptor.psbt_redeem_script(); | ||||
|             psbt_input.witness_script = derived_descriptor.psbt_witness_script(); | ||||
| @ -957,7 +874,7 @@ where | ||||
|                 .borrow() | ||||
|                 .get_path_from_script_pubkey(&tx_output.script_pubkey)? | ||||
|             { | ||||
|                 let (desc, _) = self.get_descriptor_for(script_type); | ||||
|                 let (desc, _) = self.get_descriptor_for_script_type(script_type); | ||||
|                 psbt_output.hd_keypaths = desc.get_hd_keypaths(child)?; | ||||
|             } | ||||
|         } | ||||
| @ -982,7 +899,7 @@ where | ||||
|                     debug!("Found descriptor {:?}/{}", script_type, child); | ||||
| 
 | ||||
|                     // merge hd_keypaths
 | ||||
|                     let (desc, _) = self.get_descriptor_for(script_type); | ||||
|                     let (desc, _) = self.get_descriptor_for_script_type(script_type); | ||||
|                     let mut hd_keypaths = desc.get_hd_keypaths(child)?; | ||||
|                     psbt_input.hd_keypaths.append(&mut hd_keypaths); | ||||
|                 } | ||||
| @ -1086,13 +1003,12 @@ where | ||||
| 
 | ||||
| #[cfg(test)] | ||||
| mod test { | ||||
|     use bitcoin::Network; | ||||
|     use std::str::FromStr; | ||||
| 
 | ||||
|     use miniscript::Descriptor; | ||||
|     use bitcoin::Network; | ||||
| 
 | ||||
|     use crate::database::memory::MemoryDatabase; | ||||
|     use crate::database::Database; | ||||
|     use crate::descriptor::ExtendedDescriptor; | ||||
|     use crate::types::ScriptType; | ||||
| 
 | ||||
|     use super::*; | ||||
| @ -1205,12 +1121,12 @@ mod test { | ||||
|         descriptor: &str, | ||||
|     ) -> ( | ||||
|         OfflineWallet<MemoryDatabase>, | ||||
|         (ExtendedDescriptor, Option<ExtendedDescriptor>), | ||||
|         (String, Option<String>), | ||||
|         bitcoin::Txid, | ||||
|     ) { | ||||
|         let descriptors = testutils!(@descriptors (descriptor)); | ||||
|         let wallet: OfflineWallet<_> = Wallet::new_offline( | ||||
|             &descriptors.0.to_string(), | ||||
|             &descriptors.0, | ||||
|             None, | ||||
|             Network::Regtest, | ||||
|             MemoryDatabase::new(), | ||||
|  | ||||
							
								
								
									
										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)
 | ||||
| const DUST_LIMIT_SATOSHI: u64 = 546; | ||||
| 
 | ||||
| @ -42,6 +44,61 @@ impl std::default::Default for FeeRate { | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| pub struct After { | ||||
|     pub current_height: Option<u32>, | ||||
|     pub assume_height_reached: bool, | ||||
| } | ||||
| 
 | ||||
| impl After { | ||||
|     pub(crate) fn new(current_height: Option<u32>, assume_height_reached: bool) -> After { | ||||
|         After { | ||||
|             current_height, | ||||
|             assume_height_reached, | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl<Pk: MiniscriptKey> Satisfier<Pk> for After { | ||||
|     fn check_after(&self, n: u32) -> bool { | ||||
|         if let Some(current_height) = self.current_height { | ||||
|             current_height >= n | ||||
|         } else { | ||||
|             self.assume_height_reached | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| pub struct Older { | ||||
|     pub current_height: Option<u32>, | ||||
|     pub create_height: Option<u32>, | ||||
|     pub assume_height_reached: bool, | ||||
| } | ||||
| 
 | ||||
| impl Older { | ||||
|     pub(crate) fn new( | ||||
|         current_height: Option<u32>, | ||||
|         create_height: Option<u32>, | ||||
|         assume_height_reached: bool, | ||||
|     ) -> Older { | ||||
|         Older { | ||||
|             current_height, | ||||
|             create_height, | ||||
|             assume_height_reached, | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl<Pk: MiniscriptKey> Satisfier<Pk> for Older { | ||||
|     fn check_older(&self, n: u32) -> bool { | ||||
|         if let Some(current_height) = self.current_height { | ||||
|             // TODO: test >= / >
 | ||||
|             current_height as u64 >= self.create_height.unwrap_or(0) as u64 + n as u64 | ||||
|         } else { | ||||
|             self.assume_height_reached | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| pub struct ChunksIterator<I: Iterator> { | ||||
|     iter: I, | ||||
|     size: usize, | ||||
|  | ||||
| @ -67,11 +67,11 @@ pub fn magical_blockchain_tests(attr: TokenStream, item: TokenStream) -> TokenSt | ||||
|                     #parsed_sig_ident() | ||||
|                 } | ||||
| 
 | ||||
|                 fn get_wallet_from_descriptors(descriptors: &(ExtendedDescriptor, Option<ExtendedDescriptor>)) -> Wallet<#return_type, MemoryDatabase> { | ||||
|                     Wallet::new(&descriptors.0.to_string(), descriptors.1.as_ref().map(|d| d.to_string()).as_deref(), Network::Regtest, MemoryDatabase::new(), get_blockchain()).unwrap() | ||||
|                 fn get_wallet_from_descriptors(descriptors: &(String, Option<String>)) -> Wallet<#return_type, MemoryDatabase> { | ||||
|                     Wallet::new(&descriptors.0.to_string(), descriptors.1.as_deref(), Network::Regtest, MemoryDatabase::new(), get_blockchain()).unwrap() | ||||
|                 } | ||||
| 
 | ||||
|                 fn init_single_sig() -> (Wallet<#return_type, MemoryDatabase>, (ExtendedDescriptor, Option<ExtendedDescriptor>), TestClient) { | ||||
|                 fn init_single_sig() -> (Wallet<#return_type, MemoryDatabase>, (String, Option<String>), TestClient) { | ||||
|                     let descriptors = testutils! { | ||||
|                         @descriptors ( "wpkh(Alice)" ) ( "wpkh(Alice)" ) ( @keys ( "Alice" => (@generate_xprv "/44'/0'/0'/0/*", "/44'/0'/0'/1/*") ) ) | ||||
|                     }; | ||||
| @ -90,6 +90,7 @@ pub fn magical_blockchain_tests(attr: TokenStream, item: TokenStream) -> TokenSt | ||||
|                     let tx = testutils! { | ||||
|                         @tx ( (@external descriptors, 0) => 50_000 ) | ||||
|                     }; | ||||
|                     println!("{:?}", tx); | ||||
|                     let txid = test_client.receive(tx); | ||||
| 
 | ||||
|                     wallet.sync(noop_progress(), None).unwrap(); | ||||
| @ -272,6 +273,7 @@ pub fn magical_blockchain_tests(attr: TokenStream, item: TokenStream) -> TokenSt | ||||
|                 #[serial] | ||||
|                 fn test_sync_after_send() { | ||||
|                     let (wallet, descriptors, mut test_client) = init_single_sig(); | ||||
|                     println!("{}", descriptors.0); | ||||
|                     let node_addr = test_client.get_node_address(None); | ||||
| 
 | ||||
|                     test_client.receive(testutils! { | ||||
| @ -284,7 +286,9 @@ pub fn magical_blockchain_tests(attr: TokenStream, item: TokenStream) -> TokenSt | ||||
|                     let (psbt, details) = wallet.create_tx(TxBuilder::from_addressees(vec![(node_addr, 25_000)])).unwrap(); | ||||
|                     let (psbt, finalized) = wallet.sign(psbt, None).unwrap(); | ||||
|                     assert!(finalized, "Cannot finalize transaction"); | ||||
|                     wallet.broadcast(psbt.extract_tx()).unwrap(); | ||||
|                     let tx = psbt.extract_tx(); | ||||
|                     println!("{}", bitcoin::consensus::encode::serialize_hex(&tx)); | ||||
|                     wallet.broadcast(tx).unwrap(); | ||||
| 
 | ||||
|                     wallet.sync(noop_progress(), None).unwrap(); | ||||
|                     assert_eq!(wallet.get_balance().unwrap(), details.received); | ||||
|  | ||||
| @ -94,10 +94,16 @@ impl TestIncomingTx { | ||||
| #[macro_export] | ||||
| macro_rules! testutils { | ||||
|     ( @external $descriptors:expr, $child:expr ) => ({ | ||||
|         $descriptors.0.derive($child).expect("Derivation error").address(bitcoin::Network::Regtest).expect("No address form") | ||||
|         use miniscript::descriptor::{Descriptor, DescriptorPublicKey}; | ||||
| 
 | ||||
|         let parsed = Descriptor::<DescriptorPublicKey>::parse_secret(&$descriptors.0).expect("Failed to parse descriptor in `testutils!(@external)`").0; | ||||
|         parsed.derive(&[bitcoin::util::bip32::ChildNumber::from_normal_idx($child).unwrap()]).address(bitcoin::Network::Regtest).expect("No address form") | ||||
|     }); | ||||
|     ( @internal $descriptors:expr, $child:expr ) => ({ | ||||
|         $descriptors.1.expect("Missing internal descriptor").derive($child).expect("Derivation error").address(bitcoin::Network::Regtest).expect("No address form") | ||||
|         use miniscript::descriptor::{Descriptor, DescriptorPublicKey}; | ||||
| 
 | ||||
|         let parsed = Descriptor::<DescriptorPublicKey>::parse_secret(&$descriptors.1.expect("Missing internal descriptor")).expect("Failed to parse descriptor in `testutils!(@internal)`").0; | ||||
|         parsed.derive(&[bitcoin::util::bip32::ChildNumber::from_normal_idx($child).unwrap()]).address(bitcoin::Network::Regtest).expect("No address form") | ||||
|     }); | ||||
|     ( @e $descriptors:expr, $child:expr ) => ({ testutils!(@external $descriptors, $child) }); | ||||
|     ( @i $descriptors:expr, $child:expr ) => ({ testutils!(@internal $descriptors, $child) }); | ||||
| @ -169,6 +175,8 @@ macro_rules! testutils { | ||||
|         use std::collections::HashMap; | ||||
|         use std::convert::TryInto; | ||||
| 
 | ||||
|         use miniscript::descriptor::{Descriptor, DescriptorPublicKey}; | ||||
| 
 | ||||
|         let mut keys: HashMap<&'static str, (String, Option<String>, Option<String>)> = HashMap::new(); | ||||
|         $( | ||||
|             keys = testutils!{ @keys $( $keys )* }; | ||||
| @ -189,9 +197,9 @@ macro_rules! testutils { | ||||
|             } | ||||
| 
 | ||||
|         }).unwrap(); | ||||
|         let external: ExtendedDescriptor = external.try_into().unwrap(); | ||||
|         let external = external.to_string(); | ||||
| 
 | ||||
|         let mut internal = None::<ExtendedDescriptor>; | ||||
|         let mut internal = None::<String>; | ||||
|         $( | ||||
|             let string_internal: Descriptor<String> = FromStr::from_str($internal_descriptor).unwrap(); | ||||
| 
 | ||||
| @ -209,7 +217,7 @@ macro_rules! testutils { | ||||
|                 } | ||||
| 
 | ||||
|             }).unwrap(); | ||||
|             internal = Some(string_internal.try_into().unwrap()); | ||||
|             internal = Some(string_internal.to_string()); | ||||
|         )* | ||||
| 
 | ||||
|         (external, internal) | ||||
| @ -349,7 +357,6 @@ impl TestClient { | ||||
|         use bitcoin::blockdata::script::Builder; | ||||
|         use bitcoin::blockdata::transaction::{OutPoint, TxIn, TxOut}; | ||||
|         use bitcoin::hash_types::{BlockHash, TxMerkleNode}; | ||||
|         use bitcoin::util::hash::BitcoinHash; | ||||
| 
 | ||||
|         let block_template: serde_json::Value = self | ||||
|             .call("getblocktemplate", &[json!({"rules": ["segwit"]})]) | ||||
| @ -432,7 +439,7 @@ impl TestClient { | ||||
| 
 | ||||
|         self.wait_for_block(height as usize); | ||||
| 
 | ||||
|         block.header.bitcoin_hash().to_hex() | ||||
|         block.header.block_hash().to_hex() | ||||
|     } | ||||
| 
 | ||||
|     pub fn generate(&mut self, num_blocks: u64) { | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user