Wrap Miniscript descriptors to support xpubs
This commit is contained in:
		
							parent
							
								
									94ca008a90
								
							
						
					
					
						commit
						fa9a62fbee
					
				| @ -3,8 +3,11 @@ rust: | ||||
|   - stable | ||||
| #  - 1.31.0 | ||||
| #  - 1.22.0 | ||||
| before_script: | ||||
|   - rustup component add rustfmt | ||||
| script: | ||||
|   - cd $TRAVIS_BUILD_DIR/core/lib/ | ||||
|   - cargo fmt -- --check --verbose | ||||
|   - cargo build --verbose --all | ||||
|   - cargo test --verbose --all | ||||
| 
 | ||||
|  | ||||
| @ -2,7 +2,10 @@ | ||||
| name = "magical-bitcoin-wallet" | ||||
| version = "0.1.0" | ||||
| authors = ["Riccardo Casatta <riccardo@casatta.it>", "Alekos Filini <alekos.filini@gmail.com>"] | ||||
| edition = "2018" | ||||
| 
 | ||||
| [dependencies] | ||||
| "electrum-client" = { version = "0.1.0-beta.1", optional = true } | ||||
| log = "^0.4" | ||||
| bitcoin = { version = "0.23", features = ["use-serde"] } | ||||
| miniscript = { version = "0.12" } | ||||
| serde = { version = "^1.0", features = ["derive"] } | ||||
| serde_json = { version = "^1.0" } | ||||
|  | ||||
							
								
								
									
										27
									
								
								core/lib/examples/parse_descriptor.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								core/lib/examples/parse_descriptor.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,27 @@ | ||||
| extern crate magical_bitcoin_wallet; | ||||
| 
 | ||||
| use std::str::FromStr; | ||||
| 
 | ||||
| use magical_bitcoin_wallet::bitcoin::*; | ||||
| use magical_bitcoin_wallet::descriptor::*; | ||||
| 
 | ||||
| fn main() { | ||||
|     let desc = "sh(wsh(or_d(\ | ||||
|                     thresh_m(\ | ||||
|                       2,[d34db33f/44'/0'/0']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/1/*,tprv8ZgxMBicQKsPduL5QnGihpprdHyypMGi4DhimjtzYemu7se5YQNcZfAPLqXRuGHb5ZX2eTQj62oNqMnyxJ7B7wz54Uzswqw8fFqMVdcmVF7/1/*\
 | ||||
|                     ),\ | ||||
|                     and_v(vc:pk_h(cVt4o7BGAig1UXywgGSmARhxMdzP5qvQsxKkSsc1XEkw3tDTQFpy),older(1000))\ | ||||
|                    )))";
 | ||||
| 
 | ||||
|     let extended_desc = ExtendedDescriptor::from_str(desc).unwrap(); | ||||
|     println!("{:?}", extended_desc); | ||||
| 
 | ||||
|     let derived_desc = extended_desc.derive(42).unwrap(); | ||||
|     println!("{:?}", derived_desc); | ||||
| 
 | ||||
|     let addr = derived_desc.address(Network::Testnet).unwrap(); | ||||
|     println!("{}", addr); | ||||
| 
 | ||||
|     let script = derived_desc.witness_script(); | ||||
|     println!("{:?}", script); | ||||
| } | ||||
							
								
								
									
										26
									
								
								core/lib/src/descriptor/error.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								core/lib/src/descriptor/error.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,26 @@ | ||||
| #[derive(Debug)] | ||||
| pub enum Error { | ||||
|     InternalError, | ||||
|     InvalidPrefix(Vec<u8>), | ||||
|     HardenedDerivationOnXpub, | ||||
|     MalformedInput, | ||||
|     KeyParsingError(String), | ||||
| 
 | ||||
|     BIP32(bitcoin::util::bip32::Error), | ||||
|     Base58(bitcoin::util::base58::Error), | ||||
|     PK(bitcoin::util::key::Error), | ||||
|     Miniscript(miniscript::Error), | ||||
|     Hex(bitcoin::hashes::hex::Error), | ||||
| } | ||||
| 
 | ||||
| impl std::fmt::Display for Error { | ||||
|     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||||
|         write!(f, "{:?}", self) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl_error!(bitcoin::util::bip32::Error, BIP32); | ||||
| impl_error!(bitcoin::util::base58::Error, Base58); | ||||
| impl_error!(bitcoin::util::key::Error, PK); | ||||
| impl_error!(miniscript::Error, Miniscript); | ||||
| impl_error!(bitcoin::hashes::hex::Error, Hex); | ||||
							
								
								
									
										349
									
								
								core/lib/src/descriptor/extended_key.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										349
									
								
								core/lib/src/descriptor/extended_key.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,349 @@ | ||||
| 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> = self.path.clone().into(); | ||||
|         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.full_path(index))?; | ||||
|             Ok(ExtendedPubKey::from_private(ctx, &derive_priv)) | ||||
|         } else { | ||||
|             Ok(self.pubkey.derive_pub(ctx, &self.full_path(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, | ||||
|                 ) | ||||
|             } | ||||
|         }; | ||||
| 
 | ||||
|         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")); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										494
									
								
								core/lib/src/descriptor/mod.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										494
									
								
								core/lib/src/descriptor/mod.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,494 @@ | ||||
| use std::cell::RefCell; | ||||
| use std::collections::BTreeMap; | ||||
| use std::convert::{Into, TryFrom}; | ||||
| use std::fmt; | ||||
| use std::str::FromStr; | ||||
| 
 | ||||
| use bitcoin::blockdata::script::Script; | ||||
| use bitcoin::hashes::{hash160, Hash}; | ||||
| use bitcoin::secp256k1::{All, Secp256k1}; | ||||
| use bitcoin::util::bip32::{DerivationPath, ExtendedPrivKey, Fingerprint}; | ||||
| use bitcoin::{PrivateKey, PublicKey}; | ||||
| 
 | ||||
| pub use miniscript::descriptor::Descriptor; | ||||
| 
 | ||||
| use serde::{Deserialize, Serialize}; | ||||
| 
 | ||||
| pub mod error; | ||||
| pub mod extended_key; | ||||
| 
 | ||||
| pub use self::error::Error; | ||||
| pub use self::extended_key::{DerivationIndex, DescriptorExtendedKey}; | ||||
| 
 | ||||
| #[derive(Debug, Clone, Hash, PartialEq, PartialOrd, Eq, Ord, Default)] | ||||
| struct DummyKey(); | ||||
| 
 | ||||
| impl fmt::Display for DummyKey { | ||||
|     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||||
|         write!(f, "DummyKey") | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl std::str::FromStr for DummyKey { | ||||
|     type Err = (); | ||||
| 
 | ||||
|     fn from_str(_: &str) -> Result<Self, Self::Err> { | ||||
|         Ok(DummyKey::default()) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl miniscript::MiniscriptKey for DummyKey { | ||||
|     type Hash = DummyKey; | ||||
| 
 | ||||
|     fn to_pubkeyhash(&self) -> DummyKey { | ||||
|         DummyKey::default() | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| 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(ref pk) => { | ||||
|                 let addr = | ||||
|                     bitcoin::Address::p2shwpkh(&pk.to_public_key(), bitcoin::Network::Bitcoin); | ||||
|                 Some(addr.script_pubkey()) | ||||
|             } | ||||
|             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()), | ||||
|             _ => None, | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| trait Key: std::fmt::Debug { | ||||
|     fn fingerprint(&self, secp: &Secp256k1<All>) -> Option<Fingerprint>; | ||||
|     fn as_public_key(&self, secp: &Secp256k1<All>, index: Option<u32>) -> Result<PublicKey, Error>; | ||||
|     fn as_secret_key( | ||||
|         &self, | ||||
|         secp: &Secp256k1<All>, | ||||
|         index: Option<u32>, | ||||
|     ) -> Result<Option<PrivateKey>, Error>; | ||||
|     fn xprv(&self) -> Option<ExtendedPrivKey>; | ||||
|     fn full_path(&self, index: u32) -> Option<DerivationPath>; | ||||
|     fn is_fixed(&self) -> bool; | ||||
| } | ||||
| 
 | ||||
| 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, | ||||
|         _secp: &Secp256k1<All>, | ||||
|         _index: Option<u32>, | ||||
|     ) -> Result<Option<PrivateKey>, Error> { | ||||
|         Ok(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, | ||||
|         _secp: &Secp256k1<All>, | ||||
|         _index: Option<u32>, | ||||
|     ) -> Result<Option<PrivateKey>, Error> { | ||||
|         Ok(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> { | ||||
|         Some(self.root_xpub(secp).fingerprint()) | ||||
|     } | ||||
| 
 | ||||
|     fn as_public_key(&self, secp: &Secp256k1<All>, index: Option<u32>) -> Result<PublicKey, Error> { | ||||
|         Ok(self.derive_xpub(secp, index.unwrap_or(0))?.public_key) | ||||
|     } | ||||
| 
 | ||||
|     fn as_secret_key( | ||||
|         &self, | ||||
|         secp: &Secp256k1<All>, | ||||
|         index: Option<u32>, | ||||
|     ) -> Result<Option<PrivateKey>, Error> { | ||||
|         if self.secret.is_none() { | ||||
|             return Ok(None); | ||||
|         } | ||||
| 
 | ||||
|         let derivation_path = self.full_path(index.unwrap_or(0)); | ||||
|         Ok(Some( | ||||
|             self.secret | ||||
|                 .unwrap() | ||||
|                 .derive_priv(secp, &derivation_path)? | ||||
|                 .private_key, | ||||
|         )) | ||||
|     } | ||||
| 
 | ||||
|     fn xprv(&self) -> Option<ExtendedPrivKey> { | ||||
|         self.secret | ||||
|     } | ||||
| 
 | ||||
|     fn full_path(&self, index: u32) -> Option<DerivationPath> { | ||||
|         Some(self.full_path(index)) | ||||
|     } | ||||
| 
 | ||||
|     fn is_fixed(&self) -> bool { | ||||
|         self.final_index == DerivationIndex::Fixed | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[serde(try_from = "&str", into = "String")] | ||||
| #[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 std::clone::Clone for ExtendedDescriptor { | ||||
|     fn clone(&self) -> Self { | ||||
|         Self { | ||||
|             internal: self.internal.clone(), | ||||
|             ctx: self.ctx.clone(), | ||||
|             keys: BTreeMap::new(), | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| 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(&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) -> Vec<ExtendedPrivKey> { | ||||
|         self.keys | ||||
|             .iter() | ||||
|             .filter(|(_, v)| v.xprv().is_some()) | ||||
|             .map(|(_, v)| v.xprv().unwrap()) | ||||
|             .collect() | ||||
|     } | ||||
| 
 | ||||
|     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()) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| 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::hashes::hex::FromHex; | ||||
|     use bitcoin::{Network, PublicKey}; | ||||
| 
 | ||||
|     use crate::descriptor::*; | ||||
| 
 | ||||
|     macro_rules! hex_fingerprint { | ||||
|         ($hex:expr) => { | ||||
|             Fingerprint::from_hex($hex).unwrap() | ||||
|         }; | ||||
|     } | ||||
| 
 | ||||
|     macro_rules! hex_pubkey { | ||||
|         ($hex:expr) => { | ||||
|             PublicKey::from_str($hex).unwrap() | ||||
|         }; | ||||
|     } | ||||
| 
 | ||||
|     macro_rules! deriv_path { | ||||
|         ($str:expr) => { | ||||
|             DerivationPath::from_str($str).unwrap() | ||||
|         }; | ||||
| 
 | ||||
|         () => { | ||||
|             DerivationPath::from(vec![]) | ||||
|         }; | ||||
|     } | ||||
| 
 | ||||
|     #[test] | ||||
|     fn test_descriptor_parse_wif() { | ||||
|         let string = "pkh(cVt4o7BGAig1UXywgGSmARhxMdzP5qvQsxKkSsc1XEkw3tDTQFpy)"; | ||||
|         let desc = ExtendedDescriptor::from_str(string).unwrap(); | ||||
|         assert!(desc.is_fixed()); | ||||
|         assert_eq!( | ||||
|             desc.derive(0) | ||||
|                 .unwrap() | ||||
|                 .address(Network::Testnet) | ||||
|                 .unwrap() | ||||
|                 .to_string(), | ||||
|             "mqwpxxvfv3QbM8PU8uBx2jaNt9btQqvQNx" | ||||
|         ); | ||||
|         assert_eq!( | ||||
|             desc.derive(42) | ||||
|                 .unwrap() | ||||
|                 .address(Network::Testnet) | ||||
|                 .unwrap() | ||||
|                 .to_string(), | ||||
|             "mqwpxxvfv3QbM8PU8uBx2jaNt9btQqvQNx" | ||||
|         ); | ||||
|         assert_eq!(desc.get_secret_keys().len(), 1); | ||||
|     } | ||||
| 
 | ||||
|     #[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().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().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"))) | ||||
|         ) | ||||
|     } | ||||
| } | ||||
							
								
								
									
										21
									
								
								core/lib/src/error.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								core/lib/src/error.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,21 @@ | ||||
| #[derive(Debug)] | ||||
| pub enum Error { | ||||
|     KeyMismatch(bitcoin::secp256k1::PublicKey, bitcoin::secp256k1::PublicKey), | ||||
|     MissingInputUTXO(usize), | ||||
| 
 | ||||
|     BIP32(bitcoin::util::bip32::Error), | ||||
|     Secp256k1(bitcoin::secp256k1::Error), | ||||
| } | ||||
| 
 | ||||
| macro_rules! impl_error { | ||||
|     ( $from:ty, $to:ident ) => { | ||||
|         impl std::convert::From<$from> for Error { | ||||
|             fn from(err: $from) -> Self { | ||||
|                 Error::$to(err) | ||||
|             } | ||||
|         } | ||||
|     }; | ||||
| } | ||||
| 
 | ||||
| impl_error!(bitcoin::util::bip32::Error, BIP32); | ||||
| impl_error!(bitcoin::secp256k1::Error, Secp256k1); | ||||
| @ -1,7 +1,8 @@ | ||||
| #[cfg(test)] | ||||
| mod tests { | ||||
|     #[test] | ||||
|     fn it_works() { | ||||
|         assert_eq!(2 + 2, 4); | ||||
|     } | ||||
| } | ||||
| pub extern crate bitcoin; | ||||
| extern crate log; | ||||
| pub extern crate miniscript; | ||||
| extern crate serde; | ||||
| extern crate serde_json; | ||||
| 
 | ||||
| pub mod descriptor; | ||||
| pub mod error; | ||||
|  | ||||
							
								
								
									
										198
									
								
								core/lib/src/psbt.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										198
									
								
								core/lib/src/psbt.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,198 @@ | ||||
| use std::collections::BTreeMap; | ||||
| 
 | ||||
| use bitcoin::hashes::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}; | ||||
| 
 | ||||
| use miniscript::{BitcoinSig, Satisfier}; | ||||
| 
 | ||||
| use crate::descriptor::ExtendedDescriptor; | ||||
| use crate::error::Error; | ||||
| use crate::signer::Signer; | ||||
| 
 | ||||
| pub struct PSBTSatisfier<'a> { | ||||
|     input: &'a psbt::Input, | ||||
|     create_height: Option<u32>, | ||||
|     current_height: Option<u32>, | ||||
| } | ||||
| 
 | ||||
| impl<'a> PSBTSatisfier<'a> { | ||||
|     pub fn new( | ||||
|         input: &'a psbt::Input, | ||||
|         create_height: Option<u32>, | ||||
|         current_height: Option<u32>, | ||||
|     ) -> Self { | ||||
|         PSBTSatisfier { | ||||
|             input, | ||||
|             create_height, | ||||
|             current_height, | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| // 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> { | ||||
|         if let Some(rawsig) = self.input.partial_sigs.get(pk) { | ||||
|             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)) | ||||
|         } else { | ||||
|             None | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     fn check_older(&self, height: u32) -> bool { | ||||
|         // TODO: test >= / >
 | ||||
|         self.current_height.unwrap_or(0) >= self.create_height.unwrap_or(0) + height | ||||
|     } | ||||
| 
 | ||||
|     fn check_after(&self, height: u32) -> bool { | ||||
|         self.current_height.unwrap_or(0) > height | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[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, | ||||
|         }) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| 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))) | ||||
|             }) | ||||
|     } | ||||
| } | ||||
							
								
								
									
										87
									
								
								core/lib/src/signer.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										87
									
								
								core/lib/src/signer.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,87 @@ | ||||
| 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) | ||||
|     } | ||||
| } | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user