44
55#![ allow( non_snake_case) ] // Test names intentionally use double underscore.
66#![ allow( unused_imports) ] // Because of feature gated tests.
7-
87use bitcoin:: consensus:: encode;
98use bitcoin:: hex:: FromHex as _;
109use bitcoin:: opcodes:: all:: * ;
1110use bitcoin:: {
1211 absolute, consensus, hex, psbt, script, transaction, Amount , ScriptBuf , Transaction , TxOut ,
12+ Address , Network , hashes:: { hash160, sha256, Hash } , WPubkeyHash , WScriptHash , secp256k1, PublicKey ,
13+ script:: Builder , key:: { Secp256k1 , XOnlyPublicKey } , address:: NetworkUnchecked ,
1314} ;
1415use integration_test:: { Node , NodeExt as _, Wallet } ;
1516use node:: vtype:: * ;
1617use node:: { mtype, Input , Output } ; // All the version specific types.
18+ use rand:: Rng ;
19+
1720
1821#[ test]
1922#[ cfg( not( feature = "v17" ) ) ] // analyzepsbt was added in v0.18.
@@ -201,18 +204,63 @@ fn raw_transactions__decode_script__modelled() {
201204 let node = Node :: with_wallet ( Wallet :: Default , & [ "-txindex" ] ) ;
202205 node. fund_wallet ( ) ;
203206
204- let p2pkh = arbitrary_p2pkh_script ( ) ;
205- let multi = arbitrary_multisig_script ( ) ;
206-
207- for script in & [ p2pkh, multi] {
207+ let test_cases: Vec < ( & str , ScriptBuf , Option < & str > ) > = vec ! [
208+ ( "p2pkh" , arbitrary_p2pkh_script( ) , Some ( "pubkeyhash" ) ) ,
209+ ( "multisig" , arbitrary_multisig_script( ) , Some ( "multisig" ) ) ,
210+ ( "p2sh" , arbitrary_p2sh_script( ) , Some ( "scripthash" ) ) ,
211+ ( "bare" , arbitrary_bare_script( ) , Some ( "nonstandard" ) ) ,
212+ ( "p2wpkh" , arbitrary_p2wpkh_script( ) , Some ( "witness_v0_keyhash" ) ) ,
213+ ( "p2wsh" , arbitrary_p2wsh_script( ) , Some ( "witness_v0_scripthash" ) ) ,
214+ ( "p2tr" , arbitrary_p2tr_script( ) , Some ( "witness_v1_taproot" ) ) ,
215+ ] ;
216+
217+ for ( label, script, expected_type) in test_cases {
208218 let hex = script. to_hex_string ( ) ;
209219
210220 let json: DecodeScript = node. client . decode_script ( & hex) . expect ( "decodescript" ) ;
211221 let model: Result < mtype:: DecodeScript , DecodeScriptError > = json. into_model ( ) ;
212- model. unwrap ( ) ;
222+ let decoded = model. expect ( "DecodeScript into model" ) ;
223+
224+ println ! ( "Decoded script ({label}): {:?}" , decoded) ;
225+
226+ if let Some ( expected) = expected_type {
227+ assert_eq ! ( decoded. type_, expected, "Unexpected script type for {label}" ) ;
228+ } else {
229+ println ! ( "Skipping type check for {}" , label) ;
230+ }
231+
232+ // Address should be present for standard scripts
233+ if expected_type != Some ( "nonstandard" ) {
234+ let has_any_address = !decoded. addresses . is_empty ( ) || decoded. address . is_some ( ) ;
235+ assert ! (
236+ has_any_address,
237+ "Expected at least one address for {label}"
238+ ) ;
239+ }
213240 }
214241}
242+ fn arbitrary_p2sh_script ( ) -> ScriptBuf {
243+
244+ let redeem_script = arbitrary_multisig_script ( ) ; // or arbitrary_p2pkh_script()
245+ let redeem_script_hash = hash160:: Hash :: hash ( redeem_script. as_bytes ( ) ) ;
215246
247+ script:: Builder :: new ( )
248+ . push_opcode ( bitcoin:: opcodes:: all:: OP_HASH160 )
249+ . push_slice ( redeem_script_hash. as_byte_array ( ) ) // [u8; 20]
250+ . push_opcode ( bitcoin:: opcodes:: all:: OP_EQUAL )
251+ . into_script ( )
252+ }
253+ fn arbitrary_bare_script ( ) -> ScriptBuf {
254+ script:: Builder :: new ( )
255+ . push_opcode ( OP_RETURN )
256+ . push_slice ( b"hello" )
257+ . into_script ( )
258+ }
259+ fn arbitrary_pubkey ( ) -> PublicKey {
260+ let secp = Secp256k1 :: new ( ) ;
261+ let secret_key = secp256k1:: SecretKey :: from_slice ( & [ 1u8 ; 32 ] ) . unwrap ( ) ;
262+ PublicKey :: new ( secp256k1:: PublicKey :: from_secret_key ( & secp, & secret_key) )
263+ }
216264// Script builder code copied from rust-bitcoin script unit tests.
217265fn arbitrary_p2pkh_script ( ) -> ScriptBuf {
218266 let pubkey_hash = <[ u8 ; 20 ] >:: from_hex ( "16e1ae70ff0fa102905d4af297f6912bda6cce19" ) . unwrap ( ) ;
@@ -225,7 +273,6 @@ fn arbitrary_p2pkh_script() -> ScriptBuf {
225273 . push_opcode ( OP_CHECKSIG )
226274 . into_script ( )
227275}
228-
229276fn arbitrary_multisig_script ( ) -> ScriptBuf {
230277 let pk1 =
231278 <[ u8 ; 33 ] >:: from_hex ( "022afc20bf379bc96a2f4e9e63ffceb8652b2b6a097f63fbee6ecec2a49a48010e" )
@@ -244,6 +291,123 @@ fn arbitrary_multisig_script() -> ScriptBuf {
244291 . push_opcode ( OP_CHECKMULTISIG )
245292 . into_script ( )
246293}
294+ fn arbitrary_p2wpkh_script ( ) -> ScriptBuf {
295+ let pubkey = arbitrary_pubkey ( ) ;
296+ let pubkey_hash = hash160:: Hash :: hash ( & pubkey. to_bytes ( ) ) ;
297+
298+ // P2WPKH: 0 <20-byte pubkey hash>
299+ Builder :: new ( )
300+ . push_int ( 0 )
301+ . push_slice ( pubkey_hash. as_byte_array ( ) )
302+ . into_script ( )
303+ }
304+
305+ fn arbitrary_p2wsh_script ( ) -> ScriptBuf {
306+ let redeem_script = arbitrary_multisig_script ( ) ; // any witness script
307+ let script_hash = sha256:: Hash :: hash ( redeem_script. as_bytes ( ) ) ;
308+
309+ // P2WSH: 0 <32-byte script hash>
310+ Builder :: new ( )
311+ . push_int ( 0 )
312+ . push_slice ( script_hash. as_byte_array ( ) )
313+ . into_script ( )
314+ }
315+
316+ fn arbitrary_p2tr_script ( ) -> ScriptBuf {
317+ let secp = Secp256k1 :: new ( ) ;
318+ let sk = secp256k1:: SecretKey :: from_slice ( & [ 2u8 ; 32 ] ) . unwrap ( ) ;
319+ let internal_key = secp256k1:: PublicKey :: from_secret_key ( & secp, & sk) ;
320+ let x_only = XOnlyPublicKey :: from ( internal_key) ;
321+
322+ // Taproot output script: OP_1 <x-only pubkey>
323+ Builder :: new ( )
324+ . push_int ( 1 )
325+ . push_slice ( & x_only. serialize ( ) )
326+ . into_script ( )
327+ }
328+
329+ #[ test]
330+ fn raw_transactions__decode_script_segwit__modelled ( ) {
331+
332+ let node = Node :: with_wallet ( Wallet :: Default , & [ "-txindex" ] ) ;
333+ node. client . load_wallet ( "default" ) . ok ( ) ; // Ensure wallet is loaded
334+ node. fund_wallet ( ) ;
335+
336+ // Get a new address and script
337+ let address_unc = node
338+ . client
339+ . get_new_address ( None , None )
340+ . expect ( "getnewaddress" )
341+ . address ( )
342+ . expect ( "valid address string" ) ;
343+
344+ let address = address_unc
345+ . require_network ( Network :: Regtest )
346+ . expect ( "must be regtest" ) ;
347+
348+ assert ! (
349+ address. is_segwit( ) ,
350+ "Expected SegWit address but got {:?}" ,
351+ address
352+ ) ;
353+
354+ let script = address. script_pubkey ( ) ;
355+ let hex = script. to_hex_string ( ) ;
356+
357+ // Decode script
358+ let json = node. client . decode_script ( & hex) . expect ( "decodescript" ) ;
359+ let model: Result < mtype:: DecodeScript , DecodeScriptError > = json. into_model ( ) ;
360+ let decoded = model. expect ( "DecodeScript into model" ) ;
361+
362+ let segwit = decoded
363+ . segwit
364+ . as_ref ( )
365+ . expect ( "Expected segwit field to be present" ) ;
366+
367+ assert_eq ! (
368+ segwit. hex, script,
369+ "Segwit hex does not match script"
370+ ) ;
371+
372+ // Extract the type field
373+ let script_type = decoded
374+ . segwit
375+ . as_ref ( )
376+ . map ( |s| s. type_ . as_str ( ) )
377+ . unwrap_or_else ( || decoded. type_ . as_str ( ) ) ;
378+
379+ assert_eq ! (
380+ script_type,
381+ "witness_v0_keyhash" ,
382+ "Expected script type to be witness_v0_keyhash"
383+ ) ;
384+
385+ // Compare hex from segwit
386+ let decoded_hex = decoded
387+ . segwit
388+ . as_ref ( )
389+ . map ( |s| & s. hex )
390+ . unwrap_or_else ( || {
391+ panic ! ( "Expected segwit hex to be present" )
392+ } ) ;
393+
394+ assert_eq ! ( * decoded_hex, script, "Script hex does not match" ) ;
395+
396+ // Compare addresses from segwit or fallback
397+ let address_unc_check = address. into_unchecked ( ) ;
398+ let segwit_addresses = decoded
399+ . segwit
400+ . as_ref ( )
401+ . map ( |s| & s. addresses )
402+ . unwrap_or ( & decoded. addresses ) ;
403+
404+ assert ! (
405+ segwit_addresses. iter( ) . any( |a| a == & address_unc_check) ,
406+ "Expected address {:?} in segwit.addresses or top-level addresses: {:?}" ,
407+ address_unc_check,
408+ segwit_addresses
409+ ) ;
410+ }
247411
248412#[ test]
249413fn raw_transactions__finalize_psbt__modelled ( ) {
0 commit comments