@@ -55,7 +55,7 @@ mod key;
5555pub use self :: key:: {
5656 DefiniteDescriptorKey , DerivPaths , DescriptorKeyParseError , DescriptorMultiXKey ,
5757 DescriptorPublicKey , DescriptorSecretKey , DescriptorXKey , InnerXKey , MalformedKeyDataKind ,
58- NonDefiniteKeyError , SinglePriv , SinglePub , SinglePubKey , Wildcard ,
58+ NonDefiniteKeyError , SinglePriv , SinglePub , SinglePubKey , Wildcard , XKeyNetwork ,
5959} ;
6060
6161/// Alias type for a map of public key to secret key
@@ -927,6 +927,33 @@ impl Descriptor<DescriptorPublicKey> {
927927
928928 Ok ( descriptors)
929929 }
930+
931+ /// Check the network consistency of all extended keys in this descriptor.
932+ ///
933+ /// Returns `XKeyNetwork::NoXKeys` if no extended keys are present,
934+ /// `XKeyNetwork::Mixed` if extended keys have conflicting network prefixes,
935+ /// or `XKeyNetwork::Single(network)` if all extended keys have the same network.
936+ ///
937+ /// This can be used to prevent accidentally using testnet keys on mainnet
938+ /// and vice versa, which could lead to funds being sent to unspendable addresses.
939+ pub fn xkey_network ( & self ) -> XKeyNetwork {
940+ let mut first_network = None ;
941+
942+ for key in self . iter_pk ( ) {
943+ if let Some ( network) = key. xkey_network ( ) {
944+ match first_network {
945+ None => first_network = Some ( network) ,
946+ Some ( ref n) if * n != network => return XKeyNetwork :: Mixed ,
947+ _ => continue ,
948+ }
949+ }
950+ }
951+
952+ match first_network {
953+ Some ( network) => XKeyNetwork :: Single ( network) ,
954+ None => XKeyNetwork :: NoXKeys ,
955+ }
956+ }
930957}
931958
932959impl Descriptor < DefiniteDescriptorKey > {
@@ -977,6 +1004,33 @@ impl Descriptor<DefiniteDescriptorKey> {
9771004 Err ( e) => panic ! ( "Context errors when deriving keys: {}" , e. into_outer_err( ) ) ,
9781005 }
9791006 }
1007+
1008+ /// Check the network consistency of all extended keys in this descriptor.
1009+ ///
1010+ /// Returns `XKeyNetwork::NoXKeys` if no extended keys are present,
1011+ /// `XKeyNetwork::Mixed` if extended keys have conflicting network prefixes,
1012+ /// or `XKeyNetwork::Single(network)` if all extended keys have the same network.
1013+ ///
1014+ /// This can be used to prevent accidentally using testnet keys on mainnet
1015+ /// and vice versa, which could lead to funds being sent to unspendable addresses.
1016+ pub fn xkey_network ( & self ) -> XKeyNetwork {
1017+ let mut first_network = None ;
1018+
1019+ for key in self . iter_pk ( ) {
1020+ if let Some ( network) = key. as_descriptor_public_key ( ) . xkey_network ( ) {
1021+ match first_network {
1022+ None => first_network = Some ( network) ,
1023+ Some ( ref n) if * n != network => return XKeyNetwork :: Mixed ,
1024+ _ => continue ,
1025+ }
1026+ }
1027+ }
1028+
1029+ match first_network {
1030+ Some ( network) => XKeyNetwork :: Single ( network) ,
1031+ None => XKeyNetwork :: NoXKeys ,
1032+ }
1033+ }
9801034}
9811035
9821036impl < Pk : FromStrKey > crate :: expression:: FromTree for Descriptor < Pk > {
@@ -2421,4 +2475,229 @@ pk(03f28773c2d975288bc7d1d205c3748651b075fbc6610e58cddeeddf8f19405aa8))";
24212475 assert ! ( keys[ 1 ..] . iter( ) . any( |k| k. to_string( )
24222476 == "0250863ad64a87ae8a2fe83c1af1a8403cb53f53e486d8511dad8a04887e5b2352" ) ) ;
24232477 }
2478+
2479+ // Helper function to provide unique test keys for multi-key scenarios
2480+ fn test_keys ( ) -> ( Vec < & ' static str > , Vec < & ' static str > , Vec < & ' static str > ) {
2481+ // Unique mainnet xpubs
2482+ let mainnet_xpubs = vec ! [
2483+ "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8" ,
2484+ "xpub6ASuArnXKPbfEwhqN6e3mwBcDTgzisQN1wXN9BJcM47sSikHjJf3UFHKkNAWbWMiGj7Wf5uMash7SyYq527Hqck2AxYysAA7xmALppuCkwQ" ,
2485+ "xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB" ,
2486+ "xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH" ,
2487+ ] ;
2488+
2489+ // Unique testnet tpubs
2490+ let testnet_tpubs = vec ! [
2491+ "tpubDBrgjcxBxnXyL575sHdkpKohWu5qHKoQ7TJXKNrYznh5fVEGBv89hA8ENW7A8MFVpFUSvgLqc4Nj1WZcpePX6rrxviVtPowvMuGF5rdT2Vi" ,
2492+ "tpubD6NzVbkrYhZ4WQdzxL7NmJN7b85ePo4p6RSj9QQHF7te2RR9iUeVSGgnGkoUsB9LBRosgvNbjRv9bcsJgzgBd7QKuxDm23ZewkTRzNSLEDr" ,
2493+ "tpubD6NzVbkrYhZ4YqYr3amYH15zjxHvBkUUeadieW8AxTZC7aY2L8aPSk3tpW6yW1QnWzXAB7zoiaNMfwXPPz9S68ZCV4yWvkVXjdeksLskCed" ,
2494+ "tpubDCvNhURocXGZsLNqWcqD3syHTqPXrMSTwi8feKVwAcpi29oYKsDD3Vex7x2TDneKMVN23RbLprfxB69v94iYqdaYHsVz3kPR37NQXeqouVz" ,
2495+ ] ;
2496+
2497+ // Unique single public keys
2498+ let single_keys = vec ! [
2499+ "021d4ea7132d4e1a362ee5efd8d0b59dd4d1fe8906eefa7dd812b05a46b73d829b" ,
2500+ "025476c2e83188368da1ff3e292e7acafcdb3566bb0ad253f62fc70f07aeee6357" ,
2501+ "022f01e5e15cca351daff3843fb70f3c2f0a1bdd05e5af888a67784ef3e10a2a01" ,
2502+ "023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb" ,
2503+ ] ;
2504+
2505+ ( mainnet_xpubs, testnet_tpubs, single_keys)
2506+ }
2507+
2508+ #[ test]
2509+ fn test_descriptor_pubkey_xkey_network ( ) {
2510+ use core:: str:: FromStr ;
2511+
2512+ use bitcoin:: NetworkKind ;
2513+
2514+ use crate :: descriptor:: { DescriptorPublicKey , XKeyNetwork } ;
2515+
2516+ let ( mainnet_xpubs, testnet_tpubs, single_keys) = test_keys ( ) ;
2517+
2518+ // Basic single key scenarios
2519+ let basic_tests = vec ! [
2520+ // Single mainnet xpub
2521+ ( format!( "wpkh({})" , mainnet_xpubs[ 0 ] ) , XKeyNetwork :: Single ( NetworkKind :: Main ) ) ,
2522+ // Single testnet tpub
2523+ ( format!( "wpkh({})" , testnet_tpubs[ 0 ] ) , XKeyNetwork :: Single ( NetworkKind :: Test ) ) ,
2524+ // Single public key (no extended keys)
2525+ ( format!( "wpkh({})" , single_keys[ 0 ] ) , XKeyNetwork :: NoXKeys ) ,
2526+ ] ;
2527+
2528+ for ( desc_str, expected) in basic_tests {
2529+ let desc = Descriptor :: < DescriptorPublicKey > :: from_str ( & desc_str) . unwrap ( ) ;
2530+ assert_eq ! ( desc. xkey_network( ) , expected, "Failed for basic descriptor: {}" , desc_str) ;
2531+ }
2532+
2533+ // Multi-key descriptor combinations with unique keys
2534+ let multi_key_tests = vec ! [
2535+ // Mixed networks: mainnet + testnet
2536+ (
2537+ format!( "wsh(multi(2,{},{}))" , mainnet_xpubs[ 0 ] , testnet_tpubs[ 0 ] ) ,
2538+ XKeyNetwork :: Mixed ,
2539+ ) ,
2540+ // Consistent mainnet keys
2541+ (
2542+ format!( "wsh(multi(2,{},{}))" , mainnet_xpubs[ 0 ] , mainnet_xpubs[ 1 ] ) ,
2543+ XKeyNetwork :: Single ( NetworkKind :: Main ) ,
2544+ ) ,
2545+ // Consistent testnet multisig
2546+ (
2547+ format!(
2548+ "wsh(multi(2,{},{},{}))" ,
2549+ testnet_tpubs[ 0 ] , testnet_tpubs[ 1 ] , testnet_tpubs[ 2 ]
2550+ ) ,
2551+ XKeyNetwork :: Single ( NetworkKind :: Test ) ,
2552+ ) ,
2553+ // Sorted multisig with mixed key types
2554+ (
2555+ format!(
2556+ "wsh(sortedmulti(2,{},{},{}))" ,
2557+ mainnet_xpubs[ 0 ] , testnet_tpubs[ 0 ] , single_keys[ 0 ]
2558+ ) ,
2559+ XKeyNetwork :: Mixed ,
2560+ ) ,
2561+ // 3-of-4 multisig with all mainnet keys
2562+ (
2563+ format!(
2564+ "wsh(multi(3,{},{},{},{}))" ,
2565+ mainnet_xpubs[ 0 ] , mainnet_xpubs[ 1 ] , mainnet_xpubs[ 2 ] , mainnet_xpubs[ 3 ]
2566+ ) ,
2567+ XKeyNetwork :: Single ( NetworkKind :: Main ) ,
2568+ ) ,
2569+ ] ;
2570+
2571+ for ( desc_str, expected) in multi_key_tests {
2572+ let desc = Descriptor :: < DescriptorPublicKey > :: from_str ( & desc_str) . unwrap ( ) ;
2573+ assert_eq ! (
2574+ desc. xkey_network( ) ,
2575+ expected,
2576+ "Failed for multi-key descriptor: {}" ,
2577+ desc_str
2578+ ) ;
2579+ }
2580+
2581+ // Threshold and logical operator tests
2582+ let threshold_tests = vec ! [
2583+ // Threshold with mixed key types
2584+ (
2585+ format!(
2586+ "wsh(thresh(2,c:pk_k({}),sc:pk_k({}),sc:pk_k({})))" ,
2587+ single_keys[ 0 ] , mainnet_xpubs[ 0 ] , testnet_tpubs[ 0 ]
2588+ ) ,
2589+ XKeyNetwork :: Mixed ,
2590+ ) ,
2591+ // OR with mixed networks
2592+ (
2593+ format!( "wsh(or_d(pk({}),pk({})))" , mainnet_xpubs[ 0 ] , testnet_tpubs[ 0 ] ) ,
2594+ XKeyNetwork :: Mixed ,
2595+ ) ,
2596+ // AND with consistent mainnet keys
2597+ (
2598+ format!( "wsh(and_v(v:pk({}),pk({})))" , mainnet_xpubs[ 0 ] , mainnet_xpubs[ 1 ] ) ,
2599+ XKeyNetwork :: Single ( NetworkKind :: Main ) ,
2600+ ) ,
2601+ // Complex threshold with all testnet keys
2602+ (
2603+ format!(
2604+ "wsh(thresh(3,c:pk_k({}),sc:pk_k({}),sc:pk_k({}),sc:pk_k({})))" ,
2605+ testnet_tpubs[ 0 ] , testnet_tpubs[ 1 ] , testnet_tpubs[ 2 ] , testnet_tpubs[ 3 ]
2606+ ) ,
2607+ XKeyNetwork :: Single ( NetworkKind :: Test ) ,
2608+ ) ,
2609+ ] ;
2610+
2611+ for ( desc_str, expected) in threshold_tests {
2612+ let desc = Descriptor :: < DescriptorPublicKey > :: from_str ( & desc_str) . unwrap ( ) ;
2613+ assert_eq ! (
2614+ desc. xkey_network( ) ,
2615+ expected,
2616+ "Failed for threshold descriptor: {}" ,
2617+ desc_str
2618+ ) ;
2619+ }
2620+
2621+ // Taproot and complex miniscript tests
2622+ let complex_tests = vec ! [
2623+ // Taproot with mixed networks
2624+ ( format!( "tr({},pk({}))" , mainnet_xpubs[ 0 ] , testnet_tpubs[ 0 ] ) , XKeyNetwork :: Mixed ) ,
2625+ // Taproot with consistent mainnet keys
2626+ ( format!( "tr({},pk({}))" , mainnet_xpubs[ 0 ] , mainnet_xpubs[ 1 ] ) , XKeyNetwork :: Single ( NetworkKind :: Main ) ) ,
2627+ // HTLC-like pattern with mixed networks
2628+ ( format!( "wsh(andor(pk({}),sha256(1111111111111111111111111111111111111111111111111111111111111111),and_v(v:pkh({}),older(144))))" , mainnet_xpubs[ 0 ] , testnet_tpubs[ 0 ] ) , XKeyNetwork :: Mixed ) ,
2629+ // Multi-path spending with testnet keys
2630+ ( format!( "wsh(or_d(multi(2,{},{}),and_v(v:pk({}),older(1000))))" , testnet_tpubs[ 0 ] , testnet_tpubs[ 1 ] , testnet_tpubs[ 2 ] ) , XKeyNetwork :: Single ( NetworkKind :: Test ) ) ,
2631+ // Nested conditions with only single keys
2632+ ( format!( "wsh(thresh(3,c:pk_k({}),sc:pk_k({}),sc:pk_k({}),sc:pk_k({})))" , single_keys[ 0 ] , single_keys[ 1 ] , single_keys[ 2 ] , single_keys[ 3 ] ) , XKeyNetwork :: NoXKeys ) ,
2633+ // Complex pattern with mainnet keys
2634+ ( format!( "wsh(or_d(multi(2,{},{}),and_v(v:pk({}),older(1000))))" , mainnet_xpubs[ 0 ] , mainnet_xpubs[ 1 ] , mainnet_xpubs[ 2 ] ) , XKeyNetwork :: Single ( NetworkKind :: Main ) ) ,
2635+ ] ;
2636+
2637+ for ( desc_str, expected) in complex_tests {
2638+ let desc = Descriptor :: < DescriptorPublicKey > :: from_str ( & desc_str) . unwrap ( ) ;
2639+ assert_eq ! (
2640+ desc. xkey_network( ) ,
2641+ expected,
2642+ "Failed for complex descriptor: {}" ,
2643+ desc_str
2644+ ) ;
2645+ }
2646+ }
2647+
2648+ #[ test]
2649+ fn test_definite_descriptor_key_xkey_network ( ) {
2650+ use core:: str:: FromStr ;
2651+
2652+ use bitcoin:: NetworkKind ;
2653+
2654+ use crate :: descriptor:: { DefiniteDescriptorKey , XKeyNetwork } ;
2655+
2656+ let ( mainnet_xpubs, testnet_tpubs, single_keys) = test_keys ( ) ;
2657+
2658+ // DefiniteDescriptorKey tests (no wildcards, specific derivation paths)
2659+ let definite_key_tests = vec ! [
2660+ // Basic single key scenarios
2661+ ( format!( "wpkh({})" , mainnet_xpubs[ 0 ] ) , XKeyNetwork :: Single ( NetworkKind :: Main ) ) ,
2662+ ( format!( "wpkh({})" , testnet_tpubs[ 0 ] ) , XKeyNetwork :: Single ( NetworkKind :: Test ) ) ,
2663+ ( format!( "wpkh({})" , single_keys[ 0 ] ) , XKeyNetwork :: NoXKeys ) ,
2664+ // Multi-key scenarios with specific derivation paths
2665+ (
2666+ format!( "wsh(multi(2,{},{}))" , mainnet_xpubs[ 0 ] , testnet_tpubs[ 0 ] ) ,
2667+ XKeyNetwork :: Mixed ,
2668+ ) ,
2669+ (
2670+ format!( "wsh(multi(2,{}/0,{}/1))" , testnet_tpubs[ 0 ] , testnet_tpubs[ 1 ] ) ,
2671+ XKeyNetwork :: Single ( NetworkKind :: Test ) ,
2672+ ) ,
2673+ (
2674+ format!( "wsh(multi(2,{}/0,{}/1))" , mainnet_xpubs[ 0 ] , mainnet_xpubs[ 1 ] ) ,
2675+ XKeyNetwork :: Single ( NetworkKind :: Main ) ,
2676+ ) ,
2677+ // Sorted multisig with specific paths
2678+ (
2679+ format!(
2680+ "wsh(sortedmulti(2,{}/0,{}/1,{}))" ,
2681+ mainnet_xpubs[ 0 ] , testnet_tpubs[ 0 ] , single_keys[ 0 ]
2682+ ) ,
2683+ XKeyNetwork :: Mixed ,
2684+ ) ,
2685+ // Taproot scenarios
2686+ ( format!( "tr({})" , mainnet_xpubs[ 0 ] ) , XKeyNetwork :: Single ( NetworkKind :: Main ) ) ,
2687+ (
2688+ format!( "tr({},pk({}/0))" , mainnet_xpubs[ 0 ] , testnet_tpubs[ 0 ] ) ,
2689+ XKeyNetwork :: Mixed ,
2690+ ) ,
2691+ ] ;
2692+
2693+ for ( desc_str, expected) in definite_key_tests {
2694+ let desc = Descriptor :: < DefiniteDescriptorKey > :: from_str ( & desc_str) . unwrap ( ) ;
2695+ assert_eq ! (
2696+ desc. xkey_network( ) ,
2697+ expected,
2698+ "Failed for DefiniteDescriptorKey: {}" ,
2699+ desc_str
2700+ ) ;
2701+ }
2702+ }
24242703}
0 commit comments