// Bitcoin Dev Kit // Written in 2020 by Alekos Filini // // Copyright (c) 2020-2021 Bitcoin Dev Kit Developers // // This file is licensed under the Apache License, Version 2.0 or the MIT license // , at your option. // You may not use this file except in accordance with one or both of these // licenses. #![allow(missing_docs)] #[cfg(feature = "test-blockchains")] pub mod blockchain_tests; use bitcoin::secp256k1::{Secp256k1, Verification}; use bitcoin::{Address, PublicKey}; use miniscript::descriptor::DescriptorPublicKey; use miniscript::{Descriptor, MiniscriptKey, TranslatePk}; #[derive(Clone, Debug)] pub struct TestIncomingOutput { pub value: u64, pub to_address: String, } impl TestIncomingOutput { pub fn new(value: u64, to_address: Address) -> Self { Self { value, to_address: to_address.to_string(), } } } #[derive(Clone, Debug)] pub struct TestIncomingTx { pub output: Vec, pub min_confirmations: Option, pub locktime: Option, pub replaceable: Option, } impl TestIncomingTx { pub fn new( output: Vec, min_confirmations: Option, locktime: Option, replaceable: Option, ) -> Self { Self { output, min_confirmations, locktime, replaceable, } } pub fn add_output(&mut self, output: TestIncomingOutput) { self.output.push(output); } } #[doc(hidden)] pub trait TranslateDescriptor { // derive and translate a `Descriptor` into a `Descriptor` fn derive_translated( &self, secp: &Secp256k1, index: u32, ) -> Descriptor; } impl TranslateDescriptor for Descriptor { fn derive_translated( &self, secp: &Secp256k1, index: u32, ) -> Descriptor { let translate = |key: &DescriptorPublicKey| -> PublicKey { match key { DescriptorPublicKey::XPub(xpub) => { xpub.xkey .derive_pub(secp, &xpub.derivation_path) .expect("hardened derivation steps") .public_key } DescriptorPublicKey::SinglePub(key) => key.key, } }; self.derive(index) .translate_pk_infallible(|pk| translate(pk), |pkh| translate(pkh).to_pubkeyhash()) } } #[doc(hidden)] #[macro_export] macro_rules! testutils { ( @external $descriptors:expr, $child:expr ) => ({ use bitcoin::secp256k1::Secp256k1; use miniscript::descriptor::{Descriptor, DescriptorPublicKey, DescriptorTrait}; use $crate::testutils::TranslateDescriptor; let secp = Secp256k1::new(); let parsed = Descriptor::::parse_descriptor(&secp, &$descriptors.0).expect("Failed to parse descriptor in `testutils!(@external)`").0; parsed.derive_translated(&secp, $child).address(bitcoin::Network::Regtest).expect("No address form") }); ( @internal $descriptors:expr, $child:expr ) => ({ use bitcoin::secp256k1::Secp256k1; use miniscript::descriptor::{Descriptor, DescriptorPublicKey, DescriptorTrait}; use $crate::testutils::TranslateDescriptor; let secp = Secp256k1::new(); let parsed = Descriptor::::parse_descriptor(&secp, &$descriptors.1.expect("Missing internal descriptor")).expect("Failed to parse descriptor in `testutils!(@internal)`").0; parsed.derive_translated(&secp, $child).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) }); ( @tx ( $( ( $( $addr:tt )* ) => $amount:expr ),+ ) $( ( @locktime $locktime:expr ) )? $( ( @confirmations $confirmations:expr ) )? $( ( @replaceable $replaceable:expr ) )? ) => ({ let outs = vec![$( $crate::testutils::TestIncomingOutput::new($amount, testutils!( $($addr)* ))),+]; let locktime = None::$(.or(Some($locktime)))?; let min_confirmations = None::$(.or(Some($confirmations)))?; let replaceable = None::$(.or(Some($replaceable)))?; $crate::testutils::TestIncomingTx::new(outs, min_confirmations, locktime, replaceable) }); ( @literal $key:expr ) => ({ let key = $key.to_string(); (key, None::, None::) }); ( @generate_xprv $( $external_path:expr )? $( ,$internal_path:expr )? ) => ({ use rand::Rng; let mut seed = [0u8; 32]; rand::thread_rng().fill(&mut seed[..]); let key = bitcoin::util::bip32::ExtendedPrivKey::new_master( bitcoin::Network::Testnet, &seed, ); let external_path = None::$(.or(Some($external_path.to_string())))?; let internal_path = None::$(.or(Some($internal_path.to_string())))?; (key.unwrap().to_string(), external_path, internal_path) }); ( @generate_wif ) => ({ use rand::Rng; let mut key = [0u8; bitcoin::secp256k1::constants::SECRET_KEY_SIZE]; rand::thread_rng().fill(&mut key[..]); (bitcoin::PrivateKey { compressed: true, network: bitcoin::Network::Testnet, key: bitcoin::secp256k1::SecretKey::from_slice(&key).unwrap(), }.to_string(), None::, None::) }); ( @keys ( $( $alias:expr => ( $( $key_type:tt )* ) ),+ ) ) => ({ let mut map = std::collections::HashMap::new(); $( let alias: &str = $alias; map.insert(alias, testutils!( $($key_type)* )); )+ map }); ( @descriptors ( $external_descriptor:expr ) $( ( $internal_descriptor:expr ) )? $( ( @keys $( $keys:tt )* ) )* ) => ({ use std::str::FromStr; use std::collections::HashMap; use miniscript::descriptor::Descriptor; use miniscript::TranslatePk; #[allow(unused_assignments, unused_mut)] let mut keys: HashMap<&'static str, (String, Option, Option)> = HashMap::new(); $( keys = testutils!{ @keys $( $keys )* }; )* let external: Descriptor = FromStr::from_str($external_descriptor).unwrap(); let external: Descriptor = external.translate_pk_infallible::<_, _>(|k| { if let Some((key, ext_path, _)) = keys.get(&k.as_str()) { format!("{}{}", key, ext_path.as_ref().unwrap_or(&"".into())) } else { k.clone() } }, |kh| { if let Some((key, ext_path, _)) = keys.get(&kh.as_str()) { format!("{}{}", key, ext_path.as_ref().unwrap_or(&"".into())) } else { kh.clone() } }); let external = external.to_string(); let internal = None::$(.or({ let string_internal: Descriptor = FromStr::from_str($internal_descriptor).unwrap(); let string_internal: Descriptor = string_internal.translate_pk_infallible::<_, _>(|k| { if let Some((key, _, int_path)) = keys.get(&k.as_str()) { format!("{}{}", key, int_path.as_ref().unwrap_or(&"".into())) } else { k.clone() } }, |kh| { if let Some((key, _, int_path)) = keys.get(&kh.as_str()) { format!("{}{}", key, int_path.as_ref().unwrap_or(&"".into())) } else { kh.clone() } }); Some(string_internal.to_string()) }))?; (external, internal) }) }