@@ -2,13 +2,17 @@ use crate::bip32::NgAccountPath;
22use crate :: psbt:: {
33 Error , OutputKind , PsbtOutput , derive_account_xpub, derive_full_descriptor_pubkey,
44} ;
5- use bdk_wallet:: bitcoin:: bip32:: { ChildNumber , Xpriv } ;
5+ use bdk_wallet:: bitcoin:: bip32:: { ChildNumber , DerivationPath , KeySource , Xpriv , Xpub } ;
66use bdk_wallet:: bitcoin:: psbt;
7- use bdk_wallet:: bitcoin:: secp256k1:: { Secp256k1 , Signing } ;
7+ use bdk_wallet:: bitcoin:: secp256k1:: { PublicKey , Secp256k1 , Signing } ;
88use bdk_wallet:: bitcoin:: { Address , CompressedPublicKey , Network , TxOut } ;
9- use bdk_wallet:: descriptor:: ExtendedDescriptor ;
10- use bdk_wallet:: miniscript:: descriptor:: Wpkh ;
9+ use bdk_wallet:: descriptor:: { Descriptor , ExtendedDescriptor , Segwitv0 } ;
10+ use bdk_wallet:: keys:: DescriptorPublicKey ;
11+ use bdk_wallet:: miniscript:: descriptor:: { DerivPaths , DescriptorMultiXKey , Wildcard } ;
12+ use bdk_wallet:: miniscript:: descriptor:: { Sh , Wpkh } ;
13+ use bdk_wallet:: miniscript:: { ForEachKey , Miniscript } ;
1114use bdk_wallet:: template:: { Bip49Public , DescriptorTemplate } ;
15+ use std:: collections:: BTreeMap ;
1216
1317pub fn validate_output (
1418 output : & psbt:: Output ,
@@ -18,26 +22,95 @@ pub fn validate_output(
1822) -> Result < PsbtOutput , Error > {
1923 debug_assert ! ( txout. script_pubkey. is_p2sh( ) ) ;
2024
21- // There should be at least one.
22- if output . bip32_derivation . is_empty ( ) {
23- return Err ( Error :: ExpectedKeys { index } ) ;
24- }
25+ let redeem_script = output
26+ . redeem_script
27+ . as_ref ( )
28+ . ok_or_else ( || Error :: MissingRedeemScript { index } ) ? ;
2529
26- let ( _, source) = output
27- . bip32_derivation
28- . first_key_value ( )
29- . expect ( "the previous statement checks for at least one entry" ) ;
30+ if redeem_script. is_p2wpkh ( ) {
31+ validate_p2wpkh_nested_in_p2sh_output ( output, txout, network, index)
32+ } else if redeem_script. is_p2wsh ( ) {
33+ let witness_script = output
34+ . witness_script
35+ . as_ref ( )
36+ . ok_or_else ( || Error :: MissingWitnessScript { index } ) ?;
37+
38+ let ms = Miniscript :: < _ , Segwitv0 > :: parse ( witness_script) . unwrap ( ) ;
39+ let descriptor = Sh :: new_wsh ( ms) . map ( Descriptor :: Sh ) . unwrap ( ) ;
40+
41+ // Verify that all keys in the descriptor are in the bip32_derivation map
42+ // which should have been validated already.
43+ let are_keys_valid =
44+ descriptor. for_each_key ( |pk| output. bip32_derivation . contains_key ( & pk. inner ) ) ;
45+ if !are_keys_valid {
46+ return Err ( Error :: FraudulentOutput { index } ) ;
47+ }
48+
49+ let address = descriptor. address ( network) . unwrap ( ) ;
50+ if !address. matches_script_pubkey ( & txout. script_pubkey ) {
51+ return Err ( Error :: FraudulentOutput { index } ) ;
52+ }
53+
54+ let ( _, ( _, path) ) = output
55+ . bip32_derivation
56+ . first_key_value ( )
57+ . expect ( "at least one bip32 derivation should be present" ) ;
58+
59+ let Some ( purpose) = path. as_ref ( ) . iter ( ) . next ( ) else {
60+ return Ok ( PsbtOutput {
61+ amount : txout. value ,
62+ kind : OutputKind :: Suspicious ( address) ,
63+ } ) ;
64+ } ;
65+
66+ // TODO: Add support for other BIPs here.
67+ if matches ! ( purpose, ChildNumber :: Hardened { index: 48 } ) {
68+ // For BIP-0048 all paths used to derive an address should be equal.
69+ let mut are_paths_equal = true ;
70+ for ( _, ( _, other_path) ) in output. bip32_derivation . iter ( ) {
71+ if other_path != path {
72+ are_paths_equal = false ;
73+ break ;
74+ }
75+ }
76+
77+ if !are_paths_equal {
78+ return Ok ( PsbtOutput {
79+ amount : txout. value ,
80+ kind : OutputKind :: Suspicious ( address) ,
81+ } ) ;
82+ }
3083
31- if let Some ( purpose) = source. 1 . as_ref ( ) . iter ( ) . next ( ) {
32- match purpose {
33- ChildNumber :: Hardened { index : 49 } => {
34- return validate_p2wpkh_nested_in_p2sh_output ( output, txout, network, index) ;
84+ let maybe_account_path =
85+ NgAccountPath :: parse ( path) . map_err ( |e| Error :: invalid_path ( path. clone ( ) , e) ) ?;
86+ let Some ( account_path) = maybe_account_path else {
87+ return Ok ( PsbtOutput {
88+ amount : txout. value ,
89+ kind : OutputKind :: Suspicious ( address) ,
90+ } ) ;
91+ } ;
92+
93+ if !matches ! ( account_path. script_type, Some ( 1 ) ) {
94+ return Ok ( PsbtOutput {
95+ amount : txout. value ,
96+ kind : OutputKind :: Suspicious ( address) ,
97+ } ) ;
3598 }
36- _ => return Err ( Error :: Unimplemented ) ,
99+
100+ Ok ( PsbtOutput {
101+ amount : txout. value ,
102+ kind : OutputKind :: from_derivation_path ( path, 48 , network, address) ?,
103+ } )
104+ } else {
105+ Ok ( PsbtOutput {
106+ amount : txout. value ,
107+ kind : OutputKind :: Suspicious ( address) ,
108+ } )
37109 }
110+ } else {
111+ // TODO: Legacy P2SH (e.g. BIP45).
112+ Err ( Error :: Unimplemented )
38113 }
39-
40- Err ( Error :: Unimplemented )
41114}
42115
43116fn validate_p2wpkh_nested_in_p2sh_output (
@@ -105,3 +178,49 @@ where
105178 }
106179 }
107180}
181+
182+ /// Returns the descriptor for a P2WSH wrapped in P2SH multisig account.
183+ ///
184+ /// The `required_signers` parameter must be known before hand, by for
185+ /// example, disassembling the multisig script.
186+ pub fn wsh_multisig_descriptor (
187+ required_signers : u8 ,
188+ global_xpubs : & BTreeMap < Xpub , KeySource > ,
189+ bip32_derivations : & BTreeMap < PublicKey , KeySource > ,
190+ ) -> Result < ExtendedDescriptor , Error > {
191+ // Find the account Xpubs in the global Xpub map of the PSBT.
192+ let xpubs = bip32_derivations
193+ . iter ( )
194+ . map ( |( _, ( subpath_fingerprint, subpath) ) | {
195+ global_xpubs
196+ . iter ( )
197+ . find ( |( _, ( global_fingerprint, global_path) ) | {
198+ subpath_fingerprint == global_fingerprint
199+ && subpath. as_ref ( ) . starts_with ( global_path. as_ref ( ) )
200+ } )
201+ . ok_or_else ( || Error :: MissingGlobalXpub ( subpath. clone ( ) ) )
202+ } ) ;
203+
204+ let mut descriptor_pubkeys = Vec :: new ( ) ;
205+ for maybe_xpub in xpubs {
206+ let ( xpub, source) = maybe_xpub?;
207+
208+ let descriptor_pubkey = DescriptorPublicKey :: MultiXPub ( DescriptorMultiXKey {
209+ origin : Some ( source. clone ( ) ) ,
210+ xkey : * xpub,
211+ derivation_paths : DerivPaths :: new ( vec ! [
212+ DerivationPath :: from( vec![ ChildNumber :: Normal { index: 0 } ] ) ,
213+ DerivationPath :: from( vec![ ChildNumber :: Normal { index: 1 } ] ) ,
214+ ] )
215+ . expect ( "the vector passed should not be empty" ) ,
216+ wildcard : Wildcard :: Unhardened ,
217+ } ) ;
218+ descriptor_pubkeys. push ( descriptor_pubkey) ;
219+ }
220+
221+ Ok ( ExtendedDescriptor :: new_sh_wsh_sortedmulti (
222+ usize:: from ( required_signers) ,
223+ descriptor_pubkeys,
224+ )
225+ . unwrap ( ) )
226+ }
0 commit comments