From bc0e9c9831b880cfc659d0fca62ea4343927360b Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Thu, 1 Oct 2020 15:54:59 -0700 Subject: [PATCH 1/5] [descriptor] Add ExtractPolicy trait tests --- src/descriptor/policy.rs | 403 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 403 insertions(+) diff --git a/src/descriptor/policy.rs b/src/descriptor/policy.rs index 69441c24..a71c9a98 100644 --- a/src/descriptor/policy.rs +++ b/src/descriptor/policy.rs @@ -787,3 +787,406 @@ impl ExtractPolicy for Descriptor { } } } + +#[cfg(test)] +mod test { + + use crate::descriptor; + use crate::descriptor::{ExtractPolicy, ToWalletDescriptor}; + + use super::*; + use crate::descriptor::policy::SatisfiableItem::{Multisig, Signature, Thresh}; + use crate::keys::{DescriptorKey, ToDescriptorKey}; + use crate::wallet::signer::SignersContainer; + use bitcoin::secp256k1::{All, Secp256k1}; + use bitcoin::util::bip32; + use bitcoin::util::bip32::ChildNumber; + use bitcoin::Network; + use std::str::FromStr; + use std::sync::Arc; + + const TPRV0_STR:&str = "tprv8ZgxMBicQKsPdZXrcHNLf5JAJWFAoJ2TrstMRdSKtEggz6PddbuSkvHKM9oKJyFgZV1B7rw8oChspxyYbtmEXYyg1AjfWbL3ho3XHDpHRZf"; + const TPRV1_STR:&str = "tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N"; + + const PATH: &str = "m/44'/1'/0'/0"; + + fn setup_keys( + tprv: &str, + ) -> (DescriptorKey, DescriptorKey, Fingerprint) { + let secp: Secp256k1 = Secp256k1::new(); + + let path = bip32::DerivationPath::from_str(PATH).unwrap(); + + let tprv = bip32::ExtendedPrivKey::from_str(tprv).unwrap(); + let tpub = bip32::ExtendedPubKey::from_private(&secp, &tprv); + let fingerprint = tprv.fingerprint(&secp); + + let prvkey = (tprv, path.clone()).to_descriptor_key().unwrap(); + let pubkey = (tpub, path).to_descriptor_key().unwrap(); + + (prvkey, pubkey, fingerprint) + } + + // test ExtractPolicy trait for simple descriptors; wpkh(), sh(multi()) + + #[test] + fn test_extract_policy_for_wpkh() { + let (prvkey, pubkey, fingerprint) = setup_keys(TPRV0_STR); + + let desc = descriptor!(wpkh(pubkey)).unwrap(); + + println!("desc = {:?}", desc); // TODO remove + + let (wallet_desc, keymap) = desc.to_wallet_descriptor(Network::Testnet).unwrap(); + + let signers_container = Arc::new(SignersContainer::from(keymap)); + let policy = wallet_desc + .extract_policy(signers_container) + .unwrap() + .unwrap(); + + println!("desc policy = {:?}", policy); // TODO remove + assert!( + matches!(&policy.item, Signature(pk_or_f) if &pk_or_f.fingerprint.unwrap() == &fingerprint) + ); + assert!(matches!(&policy.contribution, Satisfaction::None)); + + let desc = descriptor!(wpkh(prvkey)).unwrap(); + let (wallet_desc, keymap) = desc.to_wallet_descriptor(Network::Testnet).unwrap(); + + let signers_container = Arc::new(SignersContainer::from(keymap)); + let policy = wallet_desc + .extract_policy(signers_container) + .unwrap() + .unwrap(); + + println!("desc policy = {:?}", policy); // TODO remove + assert!( + matches!(&policy.item, Signature(pk_or_f) if &pk_or_f.fingerprint.unwrap() == &fingerprint) + ); + assert!( + matches!(&policy.contribution, Satisfaction::Complete {condition} if condition.csv == None && condition.timelock == None) + ); + } + + // 2 pub keys descriptor, required 2 prv keys + #[test] + fn test_extract_policy_for_sh_multi_partial_0of2() { + let (_prvkey0, pubkey0, fingerprint0) = setup_keys(TPRV0_STR); + let (_prvkey1, pubkey1, fingerprint1) = setup_keys(TPRV1_STR); + + let desc = descriptor!(sh(multi 2, pubkey0, pubkey1)).unwrap(); + let (wallet_desc, keymap) = desc.to_wallet_descriptor(Network::Testnet).unwrap(); + + let signers_container = Arc::new(SignersContainer::from(keymap)); + let policy = wallet_desc + .extract_policy(signers_container) + .unwrap() + .unwrap(); + + println!("desc policy = {:?}", policy); // TODO remove + assert!( + matches!(&policy.item, Multisig { keys, threshold } if threshold == &2 + && &keys[0].fingerprint.unwrap() == &fingerprint0 + && &keys[1].fingerprint.unwrap() == &fingerprint1) + ); + + // TODO should this be "Satisfaction::None" since we have no prv keys? + // TODO should items and conditions not be empty? + assert!( + matches!(&policy.contribution, Satisfaction::Partial { n, m, items, conditions} if n == &2 + && m == &2 + && items.is_empty() + && conditions.is_empty() + ) + ); + } + + // 1 prv and 1 pub key descriptor, required 2 prv keys + #[test] + fn test_extract_policy_for_sh_multi_partial_1of2() { + let (prvkey0, _pubkey0, fingerprint0) = setup_keys(TPRV0_STR); + let (_prvkey1, pubkey1, fingerprint1) = setup_keys(TPRV1_STR); + + let desc = descriptor!(sh(multi 2, prvkey0, pubkey1)).unwrap(); + let (wallet_desc, keymap) = desc.to_wallet_descriptor(Network::Testnet).unwrap(); + + let signers_container = Arc::new(SignersContainer::from(keymap)); + let policy = wallet_desc + .extract_policy(signers_container) + .unwrap() + .unwrap(); + + println!("desc policy = {:?}", policy); // TODO remove + assert!( + matches!(&policy.item, Multisig { keys, threshold } if threshold == &2 + && &keys[0].fingerprint.unwrap() == &fingerprint0 + && &keys[1].fingerprint.unwrap() == &fingerprint1) + ); + + // TODO should this be "Satisfaction::Partial" since we have only one of two prv keys? + assert!( + matches!(&policy.contribution, Satisfaction::PartialComplete { n, m, items, conditions} if n == &2 + && m == &2 + && items.len() == 2 + && conditions.contains_key(&vec![0,1]) + ) + ); + } + + // 1 prv and 1 pub key descriptor, required 1 prv keys + #[test] + fn test_extract_policy_for_sh_multi_complete_1of2() { + let (_prvkey0, pubkey0, fingerprint0) = setup_keys(TPRV0_STR); + let (prvkey1, _pubkey1, fingerprint1) = setup_keys(TPRV1_STR); + + let desc = descriptor!(sh(multi 1, pubkey0, prvkey1)).unwrap(); + let (wallet_desc, keymap) = desc.to_wallet_descriptor(Network::Testnet).unwrap(); + + let signers_container = Arc::new(SignersContainer::from(keymap)); + let policy = wallet_desc + .extract_policy(signers_container) + .unwrap() + .unwrap(); + + println!("desc policy = {:?}", policy); // TODO remove + assert!( + matches!(&policy.item, Multisig { keys, threshold } if threshold == &1 + && &keys[0].fingerprint.unwrap() == &fingerprint0 + && &keys[1].fingerprint.unwrap() == &fingerprint1) + ); + assert!( + matches!(&policy.contribution, Satisfaction::PartialComplete { n, m, items, conditions} if n == &2 + && m == &1 + && items.len() == 2 + && conditions.contains_key(&vec![0]) + && conditions.contains_key(&vec![1]) + ) + ); + } + + // 2 prv keys descriptor, required 2 prv keys + #[test] + fn test_extract_policy_for_sh_multi_complete_2of2() { + let (prvkey0, _pubkey0, fingerprint0) = setup_keys(TPRV0_STR); + let (prvkey1, _pubkey1, fingerprint1) = setup_keys(TPRV1_STR); + + let desc = descriptor!(sh(multi 2, prvkey0, prvkey1)).unwrap(); + let (wallet_desc, keymap) = desc.to_wallet_descriptor(Network::Testnet).unwrap(); + + let signers_container = Arc::new(SignersContainer::from(keymap)); + + let policy = wallet_desc + .extract_policy(signers_container) + .unwrap() + .unwrap(); + + println!("desc policy = {:?}", policy); // TODO remove + assert!( + matches!(&policy.item, Multisig { keys, threshold } if threshold == &2 + && &keys[0].fingerprint.unwrap() == &fingerprint0 + && &keys[1].fingerprint.unwrap() == &fingerprint1) + ); + + assert!( + matches!(&policy.contribution, Satisfaction::PartialComplete { n, m, items, conditions} if n == &2 + && m == &2 + && items.len() == 2 + && conditions.contains_key(&vec![0,1]) + ) + ); + } + + // test ExtractPolicy trait with extended and single keys + + #[test] + fn test_extract_policy_for_single_wpkh() { + let (prvkey, pubkey, fingerprint) = setup_keys(TPRV0_STR); + + let desc = descriptor!(wpkh(pubkey)).unwrap(); + println!("desc = {:?}", desc); // TODO remove + + let (wallet_desc, keymap) = desc.to_wallet_descriptor(Network::Testnet).unwrap(); + + let single_key = wallet_desc.derive(&[ChildNumber::from_normal_idx(0).unwrap()]); + println!("single_key = {:?}", single_key); // TODO remove + + let signers_container = Arc::new(SignersContainer::from(keymap)); + let policy = single_key + .extract_policy(signers_container) + .unwrap() + .unwrap(); + + println!("desc policy = {:?}", policy); // TODO remove + assert!( + matches!(&policy.item, Signature(pk_or_f) if &pk_or_f.fingerprint.unwrap() == &fingerprint) + ); + assert!(matches!(&policy.contribution, Satisfaction::None)); + + let desc = descriptor!(wpkh(prvkey)).unwrap(); + let (wallet_desc, keymap) = desc.to_wallet_descriptor(Network::Testnet).unwrap(); + + let single_key = wallet_desc.derive(&[ChildNumber::from_normal_idx(0).unwrap()]); + + println!("single_key = {:?}", single_key); // TODO remove + + let signers_container = Arc::new(SignersContainer::from(keymap)); + let policy = single_key + .extract_policy(signers_container) + .unwrap() + .unwrap(); + + println!("desc policy = {:?}", policy); // TODO remove + assert!( + matches!(&policy.item, Signature(pk_or_f) if &pk_or_f.fingerprint.unwrap() == &fingerprint) + ); + assert!( + matches!(&policy.contribution, Satisfaction::Complete {condition} if condition.csv == None && condition.timelock == None) + ); + } + + // single key, 1 prv and 1 pub key descriptor, required 1 prv keys + #[test] + fn test_extract_policy_for_single_wsh_multi_complete_1of2() { + let (_prvkey0, pubkey0, fingerprint0) = setup_keys(TPRV0_STR); + let (prvkey1, _pubkey1, fingerprint1) = setup_keys(TPRV1_STR); + + let desc = descriptor!(sh(multi 1, pubkey0, prvkey1)).unwrap(); + let (wallet_desc, keymap) = desc.to_wallet_descriptor(Network::Testnet).unwrap(); + + let single_key = wallet_desc.derive(&[ChildNumber::from_normal_idx(0).unwrap()]); + println!("single_key = {:?}", single_key); // TODO remove + + let signers_container = Arc::new(SignersContainer::from(keymap)); + let policy = single_key + .extract_policy(signers_container) + .unwrap() + .unwrap(); + + println!("desc policy = {:?}", policy); // TODO remove + assert!( + matches!(&policy.item, Multisig { keys, threshold } if threshold == &1 + && &keys[0].fingerprint.unwrap() == &fingerprint0 + && &keys[1].fingerprint.unwrap() == &fingerprint1) + ); + assert!( + matches!(&policy.contribution, Satisfaction::PartialComplete { n, m, items, conditions } if n == &2 + && m == &1 + && items.len() == 2 + && conditions.contains_key(&vec![0]) + && conditions.contains_key(&vec![1]) + ) + ); + } + + // test ExtractPolicy trait with descriptors containing timelocks in a thresh() + + #[test] + fn test_extract_policy_for_wsh_multi_timelock() { + let (prvkey0, _pubkey0, _fingerprint0) = setup_keys(TPRV0_STR); + let (_prvkey1, pubkey1, _fingerprint1) = setup_keys(TPRV1_STR); + let sequence = 50; + + let desc = descriptor!(wsh ( + thresh 2, (pk prvkey0), (+s pk pubkey1), (+s+d+v older sequence) + )) + .unwrap(); + + println!("desc = {:?}", desc); // TODO remove + + let (wallet_desc, keymap) = desc.to_wallet_descriptor(Network::Testnet).unwrap(); + + let signers_container = Arc::new(SignersContainer::from(keymap)); + let policy = wallet_desc + .extract_policy(signers_container) + .unwrap() + .unwrap(); + + println!("desc policy = {:?}", policy); // TODO remove + assert!( + matches!(&policy.item, Thresh { items, threshold } if items.len() == 3 && threshold == &2) + ); + + assert!( + matches!(&policy.contribution, Satisfaction::PartialComplete { n, m, items, conditions } if n == &3 + && m == &2 + && items.len() == 3 + && conditions.get(&vec![0,1]).unwrap().iter().next().unwrap().csv.is_none() + && conditions.get(&vec![0,2]).unwrap().iter().next().unwrap().csv == Some(sequence) + && conditions.get(&vec![1,2]).unwrap().iter().next().unwrap().csv == Some(sequence) + ) + ); + } + + // - mixed timelocks should fail + + #[test] + fn test_extract_policy_for_wsh_mixed_timelocks() { + let (prvkey0, _pubkey0, _fingerprint0) = setup_keys(TPRV0_STR); + + let locktime_threshold = 500000000; // if less than this means block number, else block time in seconds + let locktime_blocks = 100; + let locktime_seconds = locktime_blocks + locktime_threshold; + + let desc = descriptor!(sh (and_v (+v pk prvkey0), (and_v (+v after locktime_seconds), (after locktime_blocks)))).unwrap(); + + println!("desc = {:?}", desc); // TODO remove + + let (wallet_desc, keymap) = desc.to_wallet_descriptor(Network::Testnet).unwrap(); + + let signers_container = Arc::new(SignersContainer::from(keymap)); + let policy = wallet_desc + .extract_policy(signers_container) + .unwrap() + .unwrap(); + + println!("desc policy = {:?}", policy); // TODO remove + + // TODO how should this fail with mixed timelocks? + } + + // - multiple timelocks of the same type should be correctly merged together + + #[test] + fn test_extract_policy_for_multiple_same_timelocks() { + let (prvkey0, _pubkey0, _fingerprint0) = setup_keys(TPRV0_STR); + + let locktime_blocks0 = 100; + let locktime_blocks1 = 200; + + let desc = descriptor!(sh (and_v (+v pk prvkey0), (and_v (+v after locktime_blocks0), (after locktime_blocks1)))).unwrap(); + + println!("desc = {:?}", desc); // TODO remove + + let (wallet_desc, keymap) = desc.to_wallet_descriptor(Network::Testnet).unwrap(); + + let signers_container = Arc::new(SignersContainer::from(keymap)); + let policy = wallet_desc + .extract_policy(signers_container) + .unwrap() + .unwrap(); + + println!("desc policy = {:?}", policy); // TODO remove + + let (prvkey1, _pubkey1, _fingerprint1) = setup_keys(TPRV0_STR); + + let locktime_seconds0 = 500000100; + let locktime_seconds1 = 500000200; + + let desc = descriptor!(sh (and_v (+v pk prvkey1), (and_v (+v after locktime_seconds0), (after locktime_seconds1)))).unwrap(); + + println!("desc = {:?}", desc); // TODO remove + + let (wallet_desc, keymap) = desc.to_wallet_descriptor(Network::Testnet).unwrap(); + + let signers_container = Arc::new(SignersContainer::from(keymap)); + let policy = wallet_desc + .extract_policy(signers_container) + .unwrap() + .unwrap(); + + println!("desc policy = {:?}", policy); // TODO remove + + // TODO how should this merge timelocks? + } +} From 9fa9a304b9f752e37395a0f6a253128bc31c7377 Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Sat, 3 Oct 2020 14:48:13 -0700 Subject: [PATCH 2/5] [descriptor] Add get_checksum tests, cleanup tests --- src/descriptor/checksum.rs | 32 ++++++++++++++++++ src/descriptor/dsl.rs | 7 ++++ src/descriptor/policy.rs | 66 ++------------------------------------ src/descriptor/template.rs | 2 ++ 4 files changed, 43 insertions(+), 64 deletions(-) diff --git a/src/descriptor/checksum.rs b/src/descriptor/checksum.rs index 15f80055..04b22601 100644 --- a/src/descriptor/checksum.rs +++ b/src/descriptor/checksum.rs @@ -92,3 +92,35 @@ pub fn get_checksum(desc: &str) -> Result { Ok(String::from_iter(chars)) } + +#[cfg(test)] +mod test { + use super::*; + use crate::descriptor::get_checksum; + + // test get_checksum() function; it should return the same value as Bitcoin Core + #[test] + fn test_get_checksum() { + let desc = "wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)"; + assert_eq!(get_checksum(desc).unwrap(), "tqz0nc62"); + + let desc = "pkh(tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/44'/1'/0'/0/*)"; + assert_eq!(get_checksum(desc).unwrap(), "lasegmfs"); + } + + #[test] + fn test_get_checksum_invalid_character() { + let sparkle_heart = vec![240, 159, 146, 150]; + let sparkle_heart = std::str::from_utf8(&sparkle_heart) + .unwrap() + .chars() + .next() + .unwrap(); + let invalid_desc = format!("wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcL{}fjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)", sparkle_heart); + + assert!(matches!( + get_checksum(&invalid_desc).err(), + Some(Error::InvalidDescriptorCharacter(invalid_char)) if invalid_char == sparkle_heart + )); + } +} diff --git a/src/descriptor/dsl.rs b/src/descriptor/dsl.rs index d53b2a4d..6b99d448 100644 --- a/src/descriptor/dsl.rs +++ b/src/descriptor/dsl.rs @@ -402,3 +402,10 @@ macro_rules! fragment { }); } + +// test the descriptor!() macro +// - at least one of each "type" of operator; ie. one modifier, one leaf_opcode, one leaf_opcode_value, etc. +// - mixing up key types that implement ToDescriptorKey in multi() or thresh() +// - verify the valid_networks returned is correctly computed based on the keys present in the descriptor +// - verify the key_maps are correctly merged together +// - verify the ScriptContext is correctly validated (i.e. passing a type that only impl ToDescriptorKey to a pkh() descriptor should throw a compilation error diff --git a/src/descriptor/policy.rs b/src/descriptor/policy.rs index a71c9a98..55b9b9a0 100644 --- a/src/descriptor/policy.rs +++ b/src/descriptor/policy.rs @@ -814,13 +814,10 @@ mod test { tprv: &str, ) -> (DescriptorKey, DescriptorKey, Fingerprint) { let secp: Secp256k1 = Secp256k1::new(); - let path = bip32::DerivationPath::from_str(PATH).unwrap(); - let tprv = bip32::ExtendedPrivKey::from_str(tprv).unwrap(); let tpub = bip32::ExtendedPubKey::from_private(&secp, &tprv); let fingerprint = tprv.fingerprint(&secp); - let prvkey = (tprv, path.clone()).to_descriptor_key().unwrap(); let pubkey = (tpub, path).to_descriptor_key().unwrap(); @@ -832,20 +829,14 @@ mod test { #[test] fn test_extract_policy_for_wpkh() { let (prvkey, pubkey, fingerprint) = setup_keys(TPRV0_STR); - let desc = descriptor!(wpkh(pubkey)).unwrap(); - - println!("desc = {:?}", desc); // TODO remove - let (wallet_desc, keymap) = desc.to_wallet_descriptor(Network::Testnet).unwrap(); - let signers_container = Arc::new(SignersContainer::from(keymap)); let policy = wallet_desc .extract_policy(signers_container) .unwrap() .unwrap(); - println!("desc policy = {:?}", policy); // TODO remove assert!( matches!(&policy.item, Signature(pk_or_f) if &pk_or_f.fingerprint.unwrap() == &fingerprint) ); @@ -853,14 +844,12 @@ mod test { let desc = descriptor!(wpkh(prvkey)).unwrap(); let (wallet_desc, keymap) = desc.to_wallet_descriptor(Network::Testnet).unwrap(); - let signers_container = Arc::new(SignersContainer::from(keymap)); let policy = wallet_desc .extract_policy(signers_container) .unwrap() .unwrap(); - println!("desc policy = {:?}", policy); // TODO remove assert!( matches!(&policy.item, Signature(pk_or_f) if &pk_or_f.fingerprint.unwrap() == &fingerprint) ); @@ -874,17 +863,14 @@ mod test { fn test_extract_policy_for_sh_multi_partial_0of2() { let (_prvkey0, pubkey0, fingerprint0) = setup_keys(TPRV0_STR); let (_prvkey1, pubkey1, fingerprint1) = setup_keys(TPRV1_STR); - let desc = descriptor!(sh(multi 2, pubkey0, pubkey1)).unwrap(); let (wallet_desc, keymap) = desc.to_wallet_descriptor(Network::Testnet).unwrap(); - let signers_container = Arc::new(SignersContainer::from(keymap)); let policy = wallet_desc .extract_policy(signers_container) .unwrap() .unwrap(); - println!("desc policy = {:?}", policy); // TODO remove assert!( matches!(&policy.item, Multisig { keys, threshold } if threshold == &2 && &keys[0].fingerprint.unwrap() == &fingerprint0 @@ -907,17 +893,14 @@ mod test { fn test_extract_policy_for_sh_multi_partial_1of2() { let (prvkey0, _pubkey0, fingerprint0) = setup_keys(TPRV0_STR); let (_prvkey1, pubkey1, fingerprint1) = setup_keys(TPRV1_STR); - let desc = descriptor!(sh(multi 2, prvkey0, pubkey1)).unwrap(); let (wallet_desc, keymap) = desc.to_wallet_descriptor(Network::Testnet).unwrap(); - let signers_container = Arc::new(SignersContainer::from(keymap)); let policy = wallet_desc .extract_policy(signers_container) .unwrap() .unwrap(); - println!("desc policy = {:?}", policy); // TODO remove assert!( matches!(&policy.item, Multisig { keys, threshold } if threshold == &2 && &keys[0].fingerprint.unwrap() == &fingerprint0 @@ -939,17 +922,14 @@ mod test { fn test_extract_policy_for_sh_multi_complete_1of2() { let (_prvkey0, pubkey0, fingerprint0) = setup_keys(TPRV0_STR); let (prvkey1, _pubkey1, fingerprint1) = setup_keys(TPRV1_STR); - let desc = descriptor!(sh(multi 1, pubkey0, prvkey1)).unwrap(); let (wallet_desc, keymap) = desc.to_wallet_descriptor(Network::Testnet).unwrap(); - let signers_container = Arc::new(SignersContainer::from(keymap)); let policy = wallet_desc .extract_policy(signers_container) .unwrap() .unwrap(); - println!("desc policy = {:?}", policy); // TODO remove assert!( matches!(&policy.item, Multisig { keys, threshold } if threshold == &1 && &keys[0].fingerprint.unwrap() == &fingerprint0 @@ -970,18 +950,14 @@ mod test { fn test_extract_policy_for_sh_multi_complete_2of2() { let (prvkey0, _pubkey0, fingerprint0) = setup_keys(TPRV0_STR); let (prvkey1, _pubkey1, fingerprint1) = setup_keys(TPRV1_STR); - let desc = descriptor!(sh(multi 2, prvkey0, prvkey1)).unwrap(); let (wallet_desc, keymap) = desc.to_wallet_descriptor(Network::Testnet).unwrap(); - let signers_container = Arc::new(SignersContainer::from(keymap)); - let policy = wallet_desc .extract_policy(signers_container) .unwrap() .unwrap(); - println!("desc policy = {:?}", policy); // TODO remove assert!( matches!(&policy.item, Multisig { keys, threshold } if threshold == &2 && &keys[0].fingerprint.unwrap() == &fingerprint0 @@ -1002,22 +978,15 @@ mod test { #[test] fn test_extract_policy_for_single_wpkh() { let (prvkey, pubkey, fingerprint) = setup_keys(TPRV0_STR); - let desc = descriptor!(wpkh(pubkey)).unwrap(); - println!("desc = {:?}", desc); // TODO remove - let (wallet_desc, keymap) = desc.to_wallet_descriptor(Network::Testnet).unwrap(); - let single_key = wallet_desc.derive(&[ChildNumber::from_normal_idx(0).unwrap()]); - println!("single_key = {:?}", single_key); // TODO remove - let signers_container = Arc::new(SignersContainer::from(keymap)); let policy = single_key .extract_policy(signers_container) .unwrap() .unwrap(); - println!("desc policy = {:?}", policy); // TODO remove assert!( matches!(&policy.item, Signature(pk_or_f) if &pk_or_f.fingerprint.unwrap() == &fingerprint) ); @@ -1025,18 +994,13 @@ mod test { let desc = descriptor!(wpkh(prvkey)).unwrap(); let (wallet_desc, keymap) = desc.to_wallet_descriptor(Network::Testnet).unwrap(); - let single_key = wallet_desc.derive(&[ChildNumber::from_normal_idx(0).unwrap()]); - - println!("single_key = {:?}", single_key); // TODO remove - let signers_container = Arc::new(SignersContainer::from(keymap)); let policy = single_key .extract_policy(signers_container) .unwrap() .unwrap(); - println!("desc policy = {:?}", policy); // TODO remove assert!( matches!(&policy.item, Signature(pk_or_f) if &pk_or_f.fingerprint.unwrap() == &fingerprint) ); @@ -1050,20 +1014,15 @@ mod test { fn test_extract_policy_for_single_wsh_multi_complete_1of2() { let (_prvkey0, pubkey0, fingerprint0) = setup_keys(TPRV0_STR); let (prvkey1, _pubkey1, fingerprint1) = setup_keys(TPRV1_STR); - let desc = descriptor!(sh(multi 1, pubkey0, prvkey1)).unwrap(); let (wallet_desc, keymap) = desc.to_wallet_descriptor(Network::Testnet).unwrap(); - let single_key = wallet_desc.derive(&[ChildNumber::from_normal_idx(0).unwrap()]); - println!("single_key = {:?}", single_key); // TODO remove - let signers_container = Arc::new(SignersContainer::from(keymap)); let policy = single_key .extract_policy(signers_container) .unwrap() .unwrap(); - println!("desc policy = {:?}", policy); // TODO remove assert!( matches!(&policy.item, Multisig { keys, threshold } if threshold == &1 && &keys[0].fingerprint.unwrap() == &fingerprint0 @@ -1086,23 +1045,18 @@ mod test { let (prvkey0, _pubkey0, _fingerprint0) = setup_keys(TPRV0_STR); let (_prvkey1, pubkey1, _fingerprint1) = setup_keys(TPRV1_STR); let sequence = 50; - let desc = descriptor!(wsh ( thresh 2, (pk prvkey0), (+s pk pubkey1), (+s+d+v older sequence) )) .unwrap(); - println!("desc = {:?}", desc); // TODO remove - let (wallet_desc, keymap) = desc.to_wallet_descriptor(Network::Testnet).unwrap(); - let signers_container = Arc::new(SignersContainer::from(keymap)); let policy = wallet_desc .extract_policy(signers_container) .unwrap() .unwrap(); - println!("desc policy = {:?}", policy); // TODO remove assert!( matches!(&policy.item, Thresh { items, threshold } if items.len() == 3 && threshold == &2) ); @@ -1123,17 +1077,11 @@ mod test { #[test] fn test_extract_policy_for_wsh_mixed_timelocks() { let (prvkey0, _pubkey0, _fingerprint0) = setup_keys(TPRV0_STR); - let locktime_threshold = 500000000; // if less than this means block number, else block time in seconds let locktime_blocks = 100; let locktime_seconds = locktime_blocks + locktime_threshold; - let desc = descriptor!(sh (and_v (+v pk prvkey0), (and_v (+v after locktime_seconds), (after locktime_blocks)))).unwrap(); - - println!("desc = {:?}", desc); // TODO remove - let (wallet_desc, keymap) = desc.to_wallet_descriptor(Network::Testnet).unwrap(); - let signers_container = Arc::new(SignersContainer::from(keymap)); let policy = wallet_desc .extract_policy(signers_container) @@ -1150,16 +1098,10 @@ mod test { #[test] fn test_extract_policy_for_multiple_same_timelocks() { let (prvkey0, _pubkey0, _fingerprint0) = setup_keys(TPRV0_STR); - let locktime_blocks0 = 100; let locktime_blocks1 = 200; - let desc = descriptor!(sh (and_v (+v pk prvkey0), (and_v (+v after locktime_blocks0), (after locktime_blocks1)))).unwrap(); - - println!("desc = {:?}", desc); // TODO remove - let (wallet_desc, keymap) = desc.to_wallet_descriptor(Network::Testnet).unwrap(); - let signers_container = Arc::new(SignersContainer::from(keymap)); let policy = wallet_desc .extract_policy(signers_container) @@ -1168,17 +1110,13 @@ mod test { println!("desc policy = {:?}", policy); // TODO remove - let (prvkey1, _pubkey1, _fingerprint1) = setup_keys(TPRV0_STR); + // TODO how should this merge timelocks? + let (prvkey1, _pubkey1, _fingerprint1) = setup_keys(TPRV0_STR); let locktime_seconds0 = 500000100; let locktime_seconds1 = 500000200; - let desc = descriptor!(sh (and_v (+v pk prvkey1), (and_v (+v after locktime_seconds0), (after locktime_seconds1)))).unwrap(); - - println!("desc = {:?}", desc); // TODO remove - let (wallet_desc, keymap) = desc.to_wallet_descriptor(Network::Testnet).unwrap(); - let signers_container = Arc::new(SignersContainer::from(keymap)); let policy = wallet_desc .extract_policy(signers_container) diff --git a/src/descriptor/template.rs b/src/descriptor/template.rs index b167ca45..6cc6b17f 100644 --- a/src/descriptor/template.rs +++ b/src/descriptor/template.rs @@ -422,3 +422,5 @@ macro_rules! expand_make_bipxx { expand_make_bipxx!(legacy, Legacy); expand_make_bipxx!(segwit_v0, Segwitv0); + +// test existing descriptor templates, make sure they are expanded to the right descriptors From e31f5306d27ecf6dffaab3bfb8aade7e19bad075 Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Mon, 12 Oct 2020 09:09:25 -0700 Subject: [PATCH 3/5] [descriptor] Add descriptor macro tests --- src/descriptor/dsl.rs | 299 ++++++++++++++++++++++++++++++++++++- src/descriptor/template.rs | 270 ++++++++++++++++++++++++++++++++- src/keys/mod.rs | 1 + src/lib.rs | 3 - 4 files changed, 563 insertions(+), 10 deletions(-) diff --git a/src/descriptor/dsl.rs b/src/descriptor/dsl.rs index 6b99d448..da4c5d24 100644 --- a/src/descriptor/dsl.rs +++ b/src/descriptor/dsl.rs @@ -403,9 +403,296 @@ macro_rules! fragment { } -// test the descriptor!() macro -// - at least one of each "type" of operator; ie. one modifier, one leaf_opcode, one leaf_opcode_value, etc. -// - mixing up key types that implement ToDescriptorKey in multi() or thresh() -// - verify the valid_networks returned is correctly computed based on the keys present in the descriptor -// - verify the key_maps are correctly merged together -// - verify the ScriptContext is correctly validated (i.e. passing a type that only impl ToDescriptorKey to a pkh() descriptor should throw a compilation error +#[cfg(test)] +mod test { + use bitcoin::hashes::hex::ToHex; + use miniscript::descriptor::{DescriptorPublicKey, KeyMap}; + use miniscript::{Descriptor, Legacy, Segwitv0}; + + use std::str::FromStr; + + use crate::descriptor::DescriptorMeta; + use crate::keys::{DescriptorKey, KeyError, ToDescriptorKey, ValidNetworks}; + use bitcoin::network::constants::Network::{Bitcoin, Regtest, Testnet}; + use bitcoin::util::bip32; + use bitcoin::util::bip32::ChildNumber; + + // test the descriptor!() macro + + // verify descriptor generates expected script(s) (if bare or pk) or address(es) + fn check( + desc: Result<(Descriptor, KeyMap, ValidNetworks), KeyError>, + is_witness: bool, + is_fixed: bool, + expected: &[&str], + ) { + let (desc, _key_map, _networks) = desc.unwrap(); + assert_eq!(desc.is_witness(), is_witness); + assert_eq!(desc.is_fixed(), is_fixed); + for i in 0..expected.len() { + let index = i as u32; + let child_desc = if desc.is_fixed() { + desc.clone() + } else { + desc.derive(&[ChildNumber::from_normal_idx(index).unwrap()]) + }; + let address = child_desc.address(Regtest); + if address.is_some() { + assert_eq!(address.unwrap().to_string(), *expected.get(i).unwrap()); + } else { + let script = child_desc.script_pubkey(); + assert_eq!(script.to_hex().as_str(), *expected.get(i).unwrap()); + } + } + } + + // - at least one of each "type" of operator; ie. one modifier, one leaf_opcode, one leaf_opcode_value, etc. + // - mixing up key types that implement ToDescriptorKey in multi() or thresh() + + // expected script for pk and bare manually created + // expected addresses created with `bitcoin-cli getdescriptorinfo` (for hash) and `bitcoin-cli deriveaddresses` + + #[test] + fn test_fixed_legacy_descriptors() { + let pubkey1 = bitcoin::PublicKey::from_str( + "03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd", + ) + .unwrap(); + let pubkey2 = bitcoin::PublicKey::from_str( + "032e58afe51f9ed8ad3cc7897f634d881fdbe49a81564629ded8156bebd2ffd1af", + ) + .unwrap(); + + check( + descriptor!(bare(multi 1,pubkey1,pubkey2)), + false, + true, + &["512103a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd21032e58afe51f9ed8ad3cc7897f634d881fdbe49a81564629ded8156bebd2ffd1af52ae"], + ); + check( + descriptor!(pk(pubkey1)), + false, + true, + &["2103a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bdac"], + ); + check( + descriptor!(pkh(pubkey1)), + false, + true, + &["muZpTpBYhxmRFuCjLc7C6BBDF32C8XVJUi"], + ); + check( + descriptor!(sh(multi 1,pubkey1,pubkey2)), + false, + true, + &["2MymURoV1bzuMnWMGiXzyomDkeuxXY7Suey"], + ); + } + + #[test] + fn test_fixed_segwitv0_descriptors() { + let pubkey1 = bitcoin::PublicKey::from_str( + "03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd", + ) + .unwrap(); + let pubkey2 = bitcoin::PublicKey::from_str( + "032e58afe51f9ed8ad3cc7897f634d881fdbe49a81564629ded8156bebd2ffd1af", + ) + .unwrap(); + + check( + descriptor!(wpkh(pubkey1)), + true, + true, + &["bcrt1qngw83fg8dz0k749cg7k3emc7v98wy0c7azaa6h"], + ); + check( + descriptor!(sh(wpkh(pubkey1))), + true, + true, + &["2N5LiC3CqzxDamRTPG1kiNv1FpNJQ7x28sb"], + ); + check( + descriptor!(wsh(multi 1,pubkey1,pubkey2)), + true, + true, + &["bcrt1qgw8jvv2hsrvjfa6q66rk6har7d32lrqm5unnf5cl63q9phxfvgps5fyfqe"], + ); + check( + descriptor!(sh(wsh(multi 1,pubkey1,pubkey2))), + true, + true, + &["2NCidRJysy7apkmE6JF5mLLaJFkrN3Ub9iy"], + ); + } + + #[test] + fn test_bip32_legacy_descriptors() { + let xprv = bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap(); + + let path = bip32::DerivationPath::from_str("m/0").unwrap(); + let desc_key = (xprv, path.clone()).to_descriptor_key().unwrap(); + check( + descriptor!(pk(desc_key)), + false, + false, + &[ + "2102363ad03c10024e1b597a5b01b9982807fb638e00b06f3b2d4a89707de3b93c37ac", + "2102063a21fd780df370ed2fc8c4b86aa5ea642630609c203009df631feb7b480dd2ac", + "2102ba2685ad1fa5891cb100f1656b2ce3801822ccb9bac0336734a6f8c1b93ebbc0ac", + ], + ); + + let desc_key = (xprv, path.clone()).to_descriptor_key().unwrap(); + check( + descriptor!(pkh(desc_key)), + false, + false, + &[ + "muvBdsVpJxpFuTHMKA47htJPdCvdt4F9DP", + "mxQSHK7DL2t1DN3xFxov1janCoXSSkrSPj", + "mfz43r15GiWo4nizmyzMNubsnkDpByFFAn", + ], + ); + + let path2 = bip32::DerivationPath::from_str("m/2147483647'/0").unwrap(); + let desc_key1 = (xprv, path).to_descriptor_key().unwrap(); + let desc_key2 = (xprv, path2).to_descriptor_key().unwrap(); + + check( + descriptor!(sh(multi 1,desc_key1,desc_key2)), + false, + false, + &[ + "2MtMDXsfwefZkEEhVViEPidvcKRUtJamJJ8", + "2MwAUZ1NYyWjhVvGTethFL6n7nZhS8WE6At", + "2MuT6Bj66HLwZd7s4SoD8XbK4GwriKEA6Gr", + ], + ); + } + + #[test] + fn test_bip32_segwitv0_descriptors() { + let xprv = bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap(); + + let path = bip32::DerivationPath::from_str("m/0").unwrap(); + let desc_key = (xprv, path.clone()).to_descriptor_key().unwrap(); + check( + descriptor!(wpkh(desc_key)), + true, + false, + &[ + "bcrt1qnhm8w9fhc8cxzgqsmqdf9fyjccyvc0gltnymu0", + "bcrt1qhylfd55rn75w9fj06zspctad5w4hz33rf0ttad", + "bcrt1qq5sq3a6k9av9d8cne0k9wcldy4nqey5yt6889r", + ], + ); + + let desc_key = (xprv, path.clone()).to_descriptor_key().unwrap(); + check( + descriptor!(sh(wpkh(desc_key))), + true, + false, + &[ + "2MxvjQCaLqZ5QxZ7XotZDQ63hZw3NPss763", + "2NDUoevN4QMzhvHDMGhKuiT2fN9HXbFRMwn", + "2NF4BEAY2jF1Fu8vqfN3NVKoFtom77pUxrx", + ], + ); + + let path2 = bip32::DerivationPath::from_str("m/2147483647'/0").unwrap(); + let desc_key1 = (xprv, path.clone()).to_descriptor_key().unwrap(); + let desc_key2 = (xprv, path2.clone()).to_descriptor_key().unwrap(); + check( + descriptor!(wsh(multi 1,desc_key1,desc_key2)), + true, + false, + &[ + "bcrt1qfxv8mxmlv5sz8q2mnuyaqdfe9jr4vvmx0csjhn092p6f4qfygfkq2hng49", + "bcrt1qerj85g243e6jlcdxpmn9spk0gefcwvu7nw7ee059d5ydzpdhkm2qwfkf5k", + "bcrt1qxkl2qss3k58q9ktc8e89pwr4gnptfpw4hju4xstxcjc0hkcae3jstluty7", + ], + ); + + let desc_key1 = (xprv, path).to_descriptor_key().unwrap(); + let desc_key2 = (xprv, path2).to_descriptor_key().unwrap(); + check( + descriptor!(sh(wsh(multi 1,desc_key1,desc_key2))), + true, + false, + &[ + "2NFCtXvx9q4ci2kvKub17iSTgvRXGctCGhz", + "2NB2PrFPv5NxWCpygas8tPrGJG2ZFgeuwJw", + "2N79ZAGo5cMi5Jt7Wo9L5YmF5GkEw7sjWdC", + ], + ); + } + + // - verify the valid_networks returned is correctly computed based on the keys present in the descriptor + #[test] + fn test_valid_networks() { + let xprv = bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap(); + let path = bip32::DerivationPath::from_str("m/0").unwrap(); + let desc_key = (xprv, path.clone()).to_descriptor_key().unwrap(); + + let (_desc, _key_map, valid_networks) = descriptor!(pkh(desc_key)).unwrap(); + assert_eq!(valid_networks, [Testnet, Regtest].iter().cloned().collect()); + + let xprv = bip32::ExtendedPrivKey::from_str("xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi").unwrap(); + let path = bip32::DerivationPath::from_str("m/10/20/30/40").unwrap(); + let desc_key = (xprv, path.clone()).to_descriptor_key().unwrap(); + + let (_desc, _key_map, valid_networks) = descriptor!(wpkh(desc_key)).unwrap(); + assert_eq!(valid_networks, [Bitcoin].iter().cloned().collect()); + } + + // - verify the key_maps are correctly merged together + #[test] + fn test_key_maps_merged() { + let xprv1 = bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap(); + let path1 = bip32::DerivationPath::from_str("m/0").unwrap(); + let desc_key1 = (xprv1, path1.clone()).to_descriptor_key().unwrap(); + + let xprv2 = bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPegBHHnq7YEgM815dG24M2Jk5RVqipgDxF1HJ1tsnT815X5Fd5FRfMVUs8NZs9XCb6y9an8hRPThnhfwfXJ36intaekySHGF").unwrap(); + let path2 = bip32::DerivationPath::from_str("m/2147483647'/0").unwrap(); + let desc_key2 = (xprv2, path2.clone()).to_descriptor_key().unwrap(); + + let xprv3 = bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPdZXrcHNLf5JAJWFAoJ2TrstMRdSKtEggz6PddbuSkvHKM9oKJyFgZV1B7rw8oChspxyYbtmEXYyg1AjfWbL3ho3XHDpHRZf").unwrap(); + let path3 = bip32::DerivationPath::from_str("m/10/20/30/40").unwrap(); + let desc_key3 = (xprv3, path3.clone()).to_descriptor_key().unwrap(); + + let (_desc, key_map, _valid_networks) = + descriptor!(sh(wsh(multi 2,desc_key1,desc_key2,desc_key3))).unwrap(); + assert_eq!(key_map.len(), 3); + + let desc_key1: DescriptorKey = + (xprv1, path1.clone()).to_descriptor_key().unwrap(); + let desc_key2: DescriptorKey = + (xprv2, path2.clone()).to_descriptor_key().unwrap(); + let desc_key3: DescriptorKey = + (xprv3, path3.clone()).to_descriptor_key().unwrap(); + + let (key1, _key_map, _valid_networks) = desc_key1.extract().unwrap(); + let (key2, _key_map, _valid_networks) = desc_key2.extract().unwrap(); + let (key3, _key_map, _valid_networks) = desc_key3.extract().unwrap(); + assert_eq!(key_map.get(&key1).unwrap().to_string(), "tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy/0/*"); + assert_eq!(key_map.get(&key2).unwrap().to_string(), "tprv8ZgxMBicQKsPegBHHnq7YEgM815dG24M2Jk5RVqipgDxF1HJ1tsnT815X5Fd5FRfMVUs8NZs9XCb6y9an8hRPThnhfwfXJ36intaekySHGF/2147483647'/0/*"); + assert_eq!(key_map.get(&key3).unwrap().to_string(), "tprv8ZgxMBicQKsPdZXrcHNLf5JAJWFAoJ2TrstMRdSKtEggz6PddbuSkvHKM9oKJyFgZV1B7rw8oChspxyYbtmEXYyg1AjfWbL3ho3XHDpHRZf/10/20/30/40/*"); + } + + // - verify the ScriptContext is correctly validated (i.e. passing a type that only impl ToDescriptorKey to a pkh() descriptor should throw a compilation error + #[test] + fn test_script_context_validation() { + // this compiles + let xprv = bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap(); + let path = bip32::DerivationPath::from_str("m/0").unwrap(); + let desc_key: DescriptorKey = (xprv, path.clone()).to_descriptor_key().unwrap(); + + let (desc, _key_map, _valid_networks) = descriptor!(pkh(desc_key)).unwrap(); + assert_eq!(desc.to_string(), "pkh(tpubD6NzVbkrYhZ4WR7a4vY1VT3khMJMeAxVsfq9TBJyJWrNk247zCJtV7AWf6UJP7rAVsn8NNKdJi3gFyKPTmWZS9iukb91xbn2HbFSMQm2igY/0/*)"); + + // as expected this does not compile due to invalid context + //let desc_key:DescriptorKey = (xprv, path.clone()).to_descriptor_key().unwrap(); + //let (desc, _key_map, _valid_networks) = descriptor!(pkh(desc_key)).unwrap(); + } +} diff --git a/src/descriptor/template.rs b/src/descriptor/template.rs index 6cc6b17f..64a4a399 100644 --- a/src/descriptor/template.rs +++ b/src/descriptor/template.rs @@ -423,4 +423,272 @@ macro_rules! expand_make_bipxx { expand_make_bipxx!(legacy, Legacy); expand_make_bipxx!(segwit_v0, Segwitv0); -// test existing descriptor templates, make sure they are expanded to the right descriptors +#[cfg(test)] +mod test { + // test existing descriptor templates, make sure they are expanded to the right descriptors + + use super::*; + use crate::descriptor::DescriptorMeta; + use crate::keys::{KeyError, ValidNetworks}; + use bitcoin::hashes::core::str::FromStr; + use bitcoin::network::constants::Network::Regtest; + use bitcoin::util::bip32::ChildNumber; + use miniscript::descriptor::{DescriptorPublicKey, KeyMap}; + use miniscript::Descriptor; + + // verify template descriptor generates expected address(es) + fn check( + desc: Result<(Descriptor, KeyMap, ValidNetworks), KeyError>, + is_witness: bool, + is_fixed: bool, + expected: &[&str], + ) { + let (desc, _key_map, _networks) = desc.unwrap(); + assert_eq!(desc.is_witness(), is_witness); + assert_eq!(desc.is_fixed(), is_fixed); + for i in 0..expected.len() { + let index = i as u32; + let child_desc = if desc.is_fixed() { + desc.clone() + } else { + desc.derive(&[ChildNumber::from_normal_idx(index).unwrap()]) + }; + let address = child_desc.address(Regtest).unwrap(); + assert_eq!(address.to_string(), *expected.get(i).unwrap()); + } + } + + // P2PKH + #[test] + fn test_p2ph_template() { + let prvkey = + bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um") + .unwrap(); + check( + P2PKH(prvkey).build(), + false, + true, + &["mwJ8hxFYW19JLuc65RCTaP4v1rzVU8cVMT"], + ); + + let pubkey = bitcoin::PublicKey::from_str( + "03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd", + ) + .unwrap(); + check( + P2PKH(pubkey).build(), + false, + true, + &["muZpTpBYhxmRFuCjLc7C6BBDF32C8XVJUi"], + ); + } + + // P2WPKH-P2SH `sh(wpkh(key))` + #[test] + fn test_p2wphp2sh_template() { + let prvkey = + bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um") + .unwrap(); + check( + P2WPKH_P2SH(prvkey).build(), + true, + true, + &["2NB4ox5VDRw1ecUv6SnT3VQHPXveYztRqk5"], + ); + + let pubkey = bitcoin::PublicKey::from_str( + "03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd", + ) + .unwrap(); + check( + P2WPKH_P2SH(pubkey).build(), + true, + true, + &["2N5LiC3CqzxDamRTPG1kiNv1FpNJQ7x28sb"], + ); + } + + // P2WPKH `wpkh(key)` + #[test] + fn test_p2wph_template() { + let prvkey = + bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um") + .unwrap(); + check( + P2WPKH(prvkey).build(), + true, + true, + &["bcrt1q4525hmgw265tl3drrl8jjta7ayffu6jfcwxx9y"], + ); + + let pubkey = bitcoin::PublicKey::from_str( + "03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd", + ) + .unwrap(); + check( + P2WPKH(pubkey).build(), + true, + true, + &["bcrt1qngw83fg8dz0k749cg7k3emc7v98wy0c7azaa6h"], + ); + } + + // BIP44 `pkh(key/44'/0'/0'/{0,1}/*)` + #[test] + fn test_bip44_template() { + let prvkey = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap(); + check( + BIP44(prvkey, ScriptType::External).build(), + false, + false, + &[ + "n453VtnjDHPyDt2fDstKSu7A3YCJoHZ5g5", + "mvfrrumXgTtwFPWDNUecBBgzuMXhYM7KRP", + "mzYvhRAuQqbdSKMVVzXNYyqihgNdRadAUQ", + ], + ); + check( + BIP44(prvkey, ScriptType::Internal).build(), + false, + false, + &[ + "muHF98X9KxEzdKrnFAX85KeHv96eXopaip", + "n4hpyLJE5ub6B5Bymv4eqFxS5KjrewSmYR", + "mgvkdv1ffmsXd2B1sRKQ5dByK3SzpG42rA", + ], + ); + } + + // BIP44 public `pkh(key/{0,1}/*)` + #[test] + fn test_bip44_public_template() { + let pubkey = bitcoin::util::bip32::ExtendedPubKey::from_str("tpubDDDzQ31JkZB7VxUr9bjvBivDdqoFLrDPyLWtLapArAi51ftfmCb2DPxwLQzX65iNcXz1DGaVvyvo6JQ6rTU73r2gqdEo8uov9QKRb7nKCSU").unwrap(); + let fingerprint = bitcoin::util::bip32::Fingerprint::from_str("c55b303f").unwrap(); + check( + BIP44Public(pubkey, fingerprint, ScriptType::External).build(), + false, + false, + &[ + "miNG7dJTzJqNbFS19svRdTCisC65dsubtR", + "n2UqaDbCjWSFJvpC84m3FjUk5UaeibCzYg", + "muCPpS6Ue7nkzeJMWDViw7Lkwr92Yc4K8g", + ], + ); + check( + BIP44Public(pubkey, fingerprint, ScriptType::Internal).build(), + false, + false, + &[ + "moDr3vJ8wpt5nNxSK55MPq797nXJb2Ru9H", + "ms7A1Yt4uTezT2XkefW12AvLoko8WfNJMG", + "mhYiyat2rtEnV77cFfQsW32y1m2ceCGHPo", + ], + ); + } + + // BIP49 `sh(wpkh(key/49'/0'/0'/{0,1}/*))` + #[test] + fn test_bip49_template() { + let prvkey = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap(); + check( + BIP49(prvkey, ScriptType::External).build(), + true, + false, + &[ + "2N9bCAJXGm168MjVwpkBdNt6ucka3PKVoUV", + "2NDckYkqrYyDMtttEav5hB3Bfw9EGAW5HtS", + "2NAFTVtksF9T4a97M7nyCjwUBD24QevZ5Z4", + ], + ); + check( + BIP49(prvkey, ScriptType::Internal).build(), + true, + false, + &[ + "2NB3pA8PnzJLGV8YEKNDFpbViZv3Bm1K6CG", + "2NBiX2Wzxngb5rPiWpUiJQ2uLVB4HBjFD4p", + "2NA8ek4CdQ6aMkveYF6AYuEYNrftB47QGTn", + ], + ); + } + + // BIP49 public `sh(wpkh(key/{0,1}/*))` + #[test] + fn test_bip49_public_template() { + let pubkey = bitcoin::util::bip32::ExtendedPubKey::from_str("tpubDC49r947KGK52X5rBWS4BLs5m9SRY3pYHnvRrm7HcybZ3BfdEsGFyzCMzayi1u58eT82ZeyFZwH7DD6Q83E3fM9CpfMtmnTygnLfP59jL9L").unwrap(); + let fingerprint = bitcoin::util::bip32::Fingerprint::from_str("c55b303f").unwrap(); + check( + BIP49Public(pubkey, fingerprint, ScriptType::External).build(), + true, + false, + &[ + "2N3K4xbVAHoiTQSwxkZjWDfKoNC27pLkYnt", + "2NCTQfJ1sZa3wQ3pPseYRHbaNEpC3AquEfX", + "2MveFxAuC8BYPzTybx7FxSzW8HSd8ATT4z7", + ], + ); + check( + BIP49Public(pubkey, fingerprint, ScriptType::Internal).build(), + true, + false, + &[ + "2NF2vttKibwyxigxtx95Zw8K7JhDbo5zPVJ", + "2Mtmyd8taksxNVWCJ4wVvaiss7QPZGcAJuH", + "2NBs3CTVYPr1HCzjB4YFsnWCPCtNg8uMEfp", + ], + ); + } + + // BIP84 `wpkh(key/84'/0'/0'/{0,1}/*)` + #[test] + fn test_bip84_template() { + let prvkey = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap(); + check( + BIP84(prvkey, ScriptType::External).build(), + true, + false, + &[ + "bcrt1qkmvk2nadgplmd57ztld8nf8v2yxkzmdvwtjf8s", + "bcrt1qx0v6zgfwe50m4kqc58cqzcyem7ay2sfl3gvqhp", + "bcrt1q4h7fq9zhxst6e69p3n882nfj649l7w9g3zccfp", + ], + ); + check( + BIP84(prvkey, ScriptType::Internal).build(), + true, + false, + &[ + "bcrt1qtrwtz00wxl69e5xex7amy4xzlxkaefg3gfdkxa", + "bcrt1qqqasfhxpkkf7zrxqnkr2sfhn74dgsrc3e3ky45", + "bcrt1qpks7n0gq74hsgsz3phn5vuazjjq0f5eqhsgyce", + ], + ); + } + + // BIP84 public `wpkh(key/{0,1}/*)` + #[test] + fn test_bip84_public_template() { + let pubkey = bitcoin::util::bip32::ExtendedPubKey::from_str("tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q").unwrap(); + let fingerprint = bitcoin::util::bip32::Fingerprint::from_str("c55b303f").unwrap(); + check( + BIP84Public(pubkey, fingerprint, ScriptType::External).build(), + true, + false, + &[ + "bcrt1qedg9fdlf8cnnqfd5mks6uz5w4kgpk2prcdvd0h", + "bcrt1q3lncdlwq3lgcaaeyruynjnlccr0ve0kakh6ana", + "bcrt1qt9800y6xl3922jy3uyl0z33jh5wfpycyhcylr9", + ], + ); + check( + BIP84Public(pubkey, fingerprint, ScriptType::Internal).build(), + true, + false, + &[ + "bcrt1qm6wqukenh7guu792lj2njgw9n78cmwsy8xy3z2", + "bcrt1q694twxtjn4nnrvnyvra769j0a23rllj5c6cgwp", + "bcrt1qhlac3c5ranv5w5emlnqs7wxhkxt8maelylcarp", + ], + ); + } +} diff --git a/src/keys/mod.rs b/src/keys/mod.rs index d8fe8bda..34b29406 100644 --- a/src/keys/mod.rs +++ b/src/keys/mod.rs @@ -68,6 +68,7 @@ pub fn merge_networks(a: &ValidNetworks, b: &ValidNetworks) -> ValidNetworks { } /// Container for public or secret keys +#[derive(Debug)] pub enum DescriptorKey { #[doc(hidden)] Public(DescriptorPublicKey, ValidNetworks, PhantomData), diff --git a/src/lib.rs b/src/lib.rs index 69fc670e..d946c4f6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -66,9 +66,6 @@ pub mod cli; extern crate testutils; #[cfg(test)] #[macro_use] -extern crate testutils_macros; -#[cfg(test)] -#[macro_use] extern crate serial_test; #[macro_use] From 3a80e87ccb060af0870f3404f90ea04431f1d132 Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Mon, 19 Oct 2020 16:58:55 -0700 Subject: [PATCH 4/5] [descriptor] Fix compile errors after rebase --- src/descriptor/dsl.rs | 2 +- src/descriptor/policy.rs | 6 +++--- src/descriptor/template.rs | 2 +- src/lib.rs | 3 +++ 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/descriptor/dsl.rs b/src/descriptor/dsl.rs index da4c5d24..9b7ef1e2 100644 --- a/src/descriptor/dsl.rs +++ b/src/descriptor/dsl.rs @@ -434,7 +434,7 @@ mod test { let child_desc = if desc.is_fixed() { desc.clone() } else { - desc.derive(&[ChildNumber::from_normal_idx(index).unwrap()]) + desc.derive(ChildNumber::from_normal_idx(index).unwrap()) }; let address = child_desc.address(Regtest); if address.is_some() { diff --git a/src/descriptor/policy.rs b/src/descriptor/policy.rs index 55b9b9a0..1b7e3bff 100644 --- a/src/descriptor/policy.rs +++ b/src/descriptor/policy.rs @@ -980,7 +980,7 @@ mod test { let (prvkey, pubkey, fingerprint) = setup_keys(TPRV0_STR); let desc = descriptor!(wpkh(pubkey)).unwrap(); let (wallet_desc, keymap) = desc.to_wallet_descriptor(Network::Testnet).unwrap(); - let single_key = wallet_desc.derive(&[ChildNumber::from_normal_idx(0).unwrap()]); + let single_key = wallet_desc.derive(ChildNumber::from_normal_idx(0).unwrap()); let signers_container = Arc::new(SignersContainer::from(keymap)); let policy = single_key .extract_policy(signers_container) @@ -994,7 +994,7 @@ mod test { let desc = descriptor!(wpkh(prvkey)).unwrap(); let (wallet_desc, keymap) = desc.to_wallet_descriptor(Network::Testnet).unwrap(); - let single_key = wallet_desc.derive(&[ChildNumber::from_normal_idx(0).unwrap()]); + let single_key = wallet_desc.derive(ChildNumber::from_normal_idx(0).unwrap()); let signers_container = Arc::new(SignersContainer::from(keymap)); let policy = single_key .extract_policy(signers_container) @@ -1016,7 +1016,7 @@ mod test { let (prvkey1, _pubkey1, fingerprint1) = setup_keys(TPRV1_STR); let desc = descriptor!(sh(multi 1, pubkey0, prvkey1)).unwrap(); let (wallet_desc, keymap) = desc.to_wallet_descriptor(Network::Testnet).unwrap(); - let single_key = wallet_desc.derive(&[ChildNumber::from_normal_idx(0).unwrap()]); + let single_key = wallet_desc.derive(ChildNumber::from_normal_idx(0).unwrap()); let signers_container = Arc::new(SignersContainer::from(keymap)); let policy = single_key .extract_policy(signers_container) diff --git a/src/descriptor/template.rs b/src/descriptor/template.rs index 64a4a399..de4dd620 100644 --- a/src/descriptor/template.rs +++ b/src/descriptor/template.rs @@ -451,7 +451,7 @@ mod test { let child_desc = if desc.is_fixed() { desc.clone() } else { - desc.derive(&[ChildNumber::from_normal_idx(index).unwrap()]) + desc.derive(ChildNumber::from_normal_idx(index).unwrap()) }; let address = child_desc.address(Regtest).unwrap(); assert_eq!(address.to_string(), *expected.get(i).unwrap()); diff --git a/src/lib.rs b/src/lib.rs index d946c4f6..69fc670e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -66,6 +66,9 @@ pub mod cli; extern crate testutils; #[cfg(test)] #[macro_use] +extern crate testutils_macros; +#[cfg(test)] +#[macro_use] extern crate serial_test; #[macro_use] From 8927d68a691c043e5a45321f87dfc5321ac31a1c Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Mon, 26 Oct 2020 12:41:22 -0700 Subject: [PATCH 5/5] [descriptor] Comment out incomplete ExtractPolicy trait tests --- src/descriptor/policy.rs | 210 +++++++++++++++++++-------------------- 1 file changed, 105 insertions(+), 105 deletions(-) diff --git a/src/descriptor/policy.rs b/src/descriptor/policy.rs index 1b7e3bff..961b1ba7 100644 --- a/src/descriptor/policy.rs +++ b/src/descriptor/policy.rs @@ -859,63 +859,63 @@ mod test { } // 2 pub keys descriptor, required 2 prv keys - #[test] - fn test_extract_policy_for_sh_multi_partial_0of2() { - let (_prvkey0, pubkey0, fingerprint0) = setup_keys(TPRV0_STR); - let (_prvkey1, pubkey1, fingerprint1) = setup_keys(TPRV1_STR); - let desc = descriptor!(sh(multi 2, pubkey0, pubkey1)).unwrap(); - let (wallet_desc, keymap) = desc.to_wallet_descriptor(Network::Testnet).unwrap(); - let signers_container = Arc::new(SignersContainer::from(keymap)); - let policy = wallet_desc - .extract_policy(signers_container) - .unwrap() - .unwrap(); - - assert!( - matches!(&policy.item, Multisig { keys, threshold } if threshold == &2 - && &keys[0].fingerprint.unwrap() == &fingerprint0 - && &keys[1].fingerprint.unwrap() == &fingerprint1) - ); - - // TODO should this be "Satisfaction::None" since we have no prv keys? - // TODO should items and conditions not be empty? - assert!( - matches!(&policy.contribution, Satisfaction::Partial { n, m, items, conditions} if n == &2 - && m == &2 - && items.is_empty() - && conditions.is_empty() - ) - ); - } + // #[test] + // fn test_extract_policy_for_sh_multi_partial_0of2() { + // let (_prvkey0, pubkey0, fingerprint0) = setup_keys(TPRV0_STR); + // let (_prvkey1, pubkey1, fingerprint1) = setup_keys(TPRV1_STR); + // let desc = descriptor!(sh(multi 2, pubkey0, pubkey1)).unwrap(); + // let (wallet_desc, keymap) = desc.to_wallet_descriptor(Network::Testnet).unwrap(); + // let signers_container = Arc::new(SignersContainer::from(keymap)); + // let policy = wallet_desc + // .extract_policy(signers_container) + // .unwrap() + // .unwrap(); + // + // assert!( + // matches!(&policy.item, Multisig { keys, threshold } if threshold == &2 + // && &keys[0].fingerprint.unwrap() == &fingerprint0 + // && &keys[1].fingerprint.unwrap() == &fingerprint1) + // ); + // + // // TODO should this be "Satisfaction::None" since we have no prv keys? + // // TODO should items and conditions not be empty? + // assert!( + // matches!(&policy.contribution, Satisfaction::Partial { n, m, items, conditions} if n == &2 + // && m == &2 + // && items.is_empty() + // && conditions.is_empty() + // ) + // ); + // } // 1 prv and 1 pub key descriptor, required 2 prv keys - #[test] - fn test_extract_policy_for_sh_multi_partial_1of2() { - let (prvkey0, _pubkey0, fingerprint0) = setup_keys(TPRV0_STR); - let (_prvkey1, pubkey1, fingerprint1) = setup_keys(TPRV1_STR); - let desc = descriptor!(sh(multi 2, prvkey0, pubkey1)).unwrap(); - let (wallet_desc, keymap) = desc.to_wallet_descriptor(Network::Testnet).unwrap(); - let signers_container = Arc::new(SignersContainer::from(keymap)); - let policy = wallet_desc - .extract_policy(signers_container) - .unwrap() - .unwrap(); - - assert!( - matches!(&policy.item, Multisig { keys, threshold } if threshold == &2 - && &keys[0].fingerprint.unwrap() == &fingerprint0 - && &keys[1].fingerprint.unwrap() == &fingerprint1) - ); - - // TODO should this be "Satisfaction::Partial" since we have only one of two prv keys? - assert!( - matches!(&policy.contribution, Satisfaction::PartialComplete { n, m, items, conditions} if n == &2 - && m == &2 - && items.len() == 2 - && conditions.contains_key(&vec![0,1]) - ) - ); - } + // #[test] + // fn test_extract_policy_for_sh_multi_partial_1of2() { + // let (prvkey0, _pubkey0, fingerprint0) = setup_keys(TPRV0_STR); + // let (_prvkey1, pubkey1, fingerprint1) = setup_keys(TPRV1_STR); + // let desc = descriptor!(sh(multi 2, prvkey0, pubkey1)).unwrap(); + // let (wallet_desc, keymap) = desc.to_wallet_descriptor(Network::Testnet).unwrap(); + // let signers_container = Arc::new(SignersContainer::from(keymap)); + // let policy = wallet_desc + // .extract_policy(signers_container) + // .unwrap() + // .unwrap(); + // + // assert!( + // matches!(&policy.item, Multisig { keys, threshold } if threshold == &2 + // && &keys[0].fingerprint.unwrap() == &fingerprint0 + // && &keys[1].fingerprint.unwrap() == &fingerprint1) + // ); + // + // // TODO should this be "Satisfaction::Partial" since we have only one of two prv keys? + // assert!( + // matches!(&policy.contribution, Satisfaction::PartialComplete { n, m, items, conditions} if n == &2 + // && m == &2 + // && items.len() == 2 + // && conditions.contains_key(&vec![0,1]) + // ) + // ); + // } // 1 prv and 1 pub key descriptor, required 1 prv keys #[test] @@ -1074,57 +1074,57 @@ mod test { // - mixed timelocks should fail - #[test] - fn test_extract_policy_for_wsh_mixed_timelocks() { - let (prvkey0, _pubkey0, _fingerprint0) = setup_keys(TPRV0_STR); - let locktime_threshold = 500000000; // if less than this means block number, else block time in seconds - let locktime_blocks = 100; - let locktime_seconds = locktime_blocks + locktime_threshold; - let desc = descriptor!(sh (and_v (+v pk prvkey0), (and_v (+v after locktime_seconds), (after locktime_blocks)))).unwrap(); - let (wallet_desc, keymap) = desc.to_wallet_descriptor(Network::Testnet).unwrap(); - let signers_container = Arc::new(SignersContainer::from(keymap)); - let policy = wallet_desc - .extract_policy(signers_container) - .unwrap() - .unwrap(); - - println!("desc policy = {:?}", policy); // TODO remove - - // TODO how should this fail with mixed timelocks? - } + // #[test] + // fn test_extract_policy_for_wsh_mixed_timelocks() { + // let (prvkey0, _pubkey0, _fingerprint0) = setup_keys(TPRV0_STR); + // let locktime_threshold = 500000000; // if less than this means block number, else block time in seconds + // let locktime_blocks = 100; + // let locktime_seconds = locktime_blocks + locktime_threshold; + // let desc = descriptor!(sh (and_v (+v pk prvkey0), (and_v (+v after locktime_seconds), (after locktime_blocks)))).unwrap(); + // let (wallet_desc, keymap) = desc.to_wallet_descriptor(Network::Testnet).unwrap(); + // let signers_container = Arc::new(SignersContainer::from(keymap)); + // let policy = wallet_desc + // .extract_policy(signers_container) + // .unwrap() + // .unwrap(); + // + // println!("desc policy = {:?}", policy); // TODO remove + // + // // TODO how should this fail with mixed timelocks? + // } // - multiple timelocks of the same type should be correctly merged together - #[test] - fn test_extract_policy_for_multiple_same_timelocks() { - let (prvkey0, _pubkey0, _fingerprint0) = setup_keys(TPRV0_STR); - let locktime_blocks0 = 100; - let locktime_blocks1 = 200; - let desc = descriptor!(sh (and_v (+v pk prvkey0), (and_v (+v after locktime_blocks0), (after locktime_blocks1)))).unwrap(); - let (wallet_desc, keymap) = desc.to_wallet_descriptor(Network::Testnet).unwrap(); - let signers_container = Arc::new(SignersContainer::from(keymap)); - let policy = wallet_desc - .extract_policy(signers_container) - .unwrap() - .unwrap(); - - println!("desc policy = {:?}", policy); // TODO remove - - // TODO how should this merge timelocks? - - let (prvkey1, _pubkey1, _fingerprint1) = setup_keys(TPRV0_STR); - let locktime_seconds0 = 500000100; - let locktime_seconds1 = 500000200; - let desc = descriptor!(sh (and_v (+v pk prvkey1), (and_v (+v after locktime_seconds0), (after locktime_seconds1)))).unwrap(); - let (wallet_desc, keymap) = desc.to_wallet_descriptor(Network::Testnet).unwrap(); - let signers_container = Arc::new(SignersContainer::from(keymap)); - let policy = wallet_desc - .extract_policy(signers_container) - .unwrap() - .unwrap(); - - println!("desc policy = {:?}", policy); // TODO remove - - // TODO how should this merge timelocks? - } + // #[test] + // fn test_extract_policy_for_multiple_same_timelocks() { + // let (prvkey0, _pubkey0, _fingerprint0) = setup_keys(TPRV0_STR); + // let locktime_blocks0 = 100; + // let locktime_blocks1 = 200; + // let desc = descriptor!(sh (and_v (+v pk prvkey0), (and_v (+v after locktime_blocks0), (after locktime_blocks1)))).unwrap(); + // let (wallet_desc, keymap) = desc.to_wallet_descriptor(Network::Testnet).unwrap(); + // let signers_container = Arc::new(SignersContainer::from(keymap)); + // let policy = wallet_desc + // .extract_policy(signers_container) + // .unwrap() + // .unwrap(); + // + // println!("desc policy = {:?}", policy); // TODO remove + // + // // TODO how should this merge timelocks? + // + // let (prvkey1, _pubkey1, _fingerprint1) = setup_keys(TPRV0_STR); + // let locktime_seconds0 = 500000100; + // let locktime_seconds1 = 500000200; + // let desc = descriptor!(sh (and_v (+v pk prvkey1), (and_v (+v after locktime_seconds0), (after locktime_seconds1)))).unwrap(); + // let (wallet_desc, keymap) = desc.to_wallet_descriptor(Network::Testnet).unwrap(); + // let signers_container = Arc::new(SignersContainer::from(keymap)); + // let policy = wallet_desc + // .extract_policy(signers_container) + // .unwrap() + // .unwrap(); + // + // println!("desc policy = {:?}", policy); // TODO remove + // + // // TODO how should this merge timelocks? + // } }