44
55#![ allow( non_snake_case) ] // Test names intentionally use double underscore.
66#![ allow( unused_imports) ] // Because of feature gated tests.
7-
8- use bitcoin:: hex:: FromHex as _;
9- use bitcoin:: opcodes:: all:: * ;
10- use bitcoin:: { absolute, transaction, consensus, script, Amount , TxOut , Transaction , ScriptBuf } ;
117use integration_test:: { Node , NodeExt as _, Wallet } ;
128use node:: { mtype, Input , Output } ;
139use node:: vtype:: * ; // All the version specific types.
10+ use bitcoin:: { hex:: FromHex as _,
11+ absolute, transaction, consensus, Amount , TxOut , Transaction ,
12+ Address , Network , ScriptBuf , script, hashes:: { hash160, sha256, Hash } ,
13+ WPubkeyHash , WScriptHash , secp256k1,
14+ PublicKey ,
15+ script:: Builder ,
16+ opcodes:: all:: * ,
17+ key:: { Secp256k1 , XOnlyPublicKey } ,
18+ address:: NetworkUnchecked ,
19+ } ;
20+ use rand:: Rng ;
21+
1422
1523#[ test]
1624#[ cfg( not( feature = "v17" ) ) ] // analyzepsbt was added in v0.18.
@@ -202,18 +210,62 @@ fn raw_transactions__decode_script__modelled() {
202210 let node = Node :: with_wallet ( Wallet :: Default , & [ "-txindex" ] ) ;
203211 node. fund_wallet ( ) ;
204212
205- let p2pkh = arbitrary_p2pkh_script ( ) ;
206- let multi = arbitrary_multisig_script ( ) ;
207-
208- for script in & [ p2pkh, multi] {
213+ let test_cases: Vec < ( & str , ScriptBuf , Option < & str > ) > = vec ! [
214+ ( "p2pkh" , arbitrary_p2pkh_script( ) , Some ( "pubkeyhash" ) ) ,
215+ ( "multisig" , arbitrary_multisig_script( ) , Some ( "multisig" ) ) ,
216+ ( "p2sh" , arbitrary_p2sh_script( ) , Some ( "scripthash" ) ) ,
217+ ( "bare" , arbitrary_bare_script( ) , Some ( "nonstandard" ) ) ,
218+ ( "p2wpkh" , arbitrary_p2wpkh_script( ) , Some ( "witness_v0_keyhash" ) ) ,
219+ ( "p2wsh" , arbitrary_p2wsh_script( ) , Some ( "witness_v0_scripthash" ) ) ,
220+ ( "p2tr" , arbitrary_p2tr_script( ) , Some ( "witness_v1_taproot" ) ) ,
221+ ] ;
222+
223+ for ( label, script, expected_type) in test_cases {
209224 let hex = script. to_hex_string ( ) ;
210225
211226 let json: DecodeScript = node. client . decode_script ( & hex) . expect ( "decodescript" ) ;
212227 let model: Result < mtype:: DecodeScript , DecodeScriptError > = json. into_model ( ) ;
213- let _ = model. expect ( "DecodeScript into model" ) ;
228+ let decoded = model. expect ( "DecodeScript into model" ) ;
229+
230+ println ! ( "Decoded script ({label}): {:?}" , decoded) ;
231+
232+ if let Some ( expected) = expected_type {
233+ assert_eq ! ( decoded. type_, expected, "Unexpected script type for {label}" ) ;
234+ } else {
235+ println ! ( "Skipping type check for {}" , label) ;
236+ }
237+
238+ // Address should be present for standard scripts
239+ if expected_type != Some ( "nonstandard" ) {
240+ assert ! (
241+ !decoded. addresses. is_empty( ) ,
242+ "Expected at least one address for {label}"
243+ ) ;
244+ }
214245 }
215246}
247+ fn arbitrary_p2sh_script ( ) -> ScriptBuf {
248+
249+ let redeem_script = arbitrary_multisig_script ( ) ; // or arbitrary_p2pkh_script()
250+ let redeem_script_hash = hash160:: Hash :: hash ( redeem_script. as_bytes ( ) ) ;
216251
252+ script:: Builder :: new ( )
253+ . push_opcode ( bitcoin:: opcodes:: all:: OP_HASH160 )
254+ . push_slice ( redeem_script_hash. as_byte_array ( ) ) // [u8; 20]
255+ . push_opcode ( bitcoin:: opcodes:: all:: OP_EQUAL )
256+ . into_script ( )
257+ }
258+ fn arbitrary_bare_script ( ) -> ScriptBuf {
259+ script:: Builder :: new ( )
260+ . push_opcode ( OP_RETURN )
261+ . push_slice ( b"hello" )
262+ . into_script ( )
263+ }
264+ fn arbitrary_pubkey ( ) -> PublicKey {
265+ let secp = Secp256k1 :: new ( ) ;
266+ let secret_key = secp256k1:: SecretKey :: from_slice ( & [ 1u8 ; 32 ] ) . unwrap ( ) ;
267+ PublicKey :: new ( secp256k1:: PublicKey :: from_secret_key ( & secp, & secret_key) )
268+ }
217269// Script builder code copied from rust-bitcoin script unit tests.
218270fn arbitrary_p2pkh_script ( ) -> ScriptBuf {
219271 let pubkey_hash = <[ u8 ; 20 ] >:: from_hex ( "16e1ae70ff0fa102905d4af297f6912bda6cce19" ) . unwrap ( ) ;
@@ -226,7 +278,6 @@ fn arbitrary_p2pkh_script() -> ScriptBuf {
226278 . push_opcode ( OP_CHECKSIG )
227279 . into_script ( )
228280}
229-
230281fn arbitrary_multisig_script ( ) -> ScriptBuf {
231282 let pk1 =
232283 <[ u8 ; 33 ] >:: from_hex ( "022afc20bf379bc96a2f4e9e63ffceb8652b2b6a097f63fbee6ecec2a49a48010e" )
@@ -245,6 +296,107 @@ fn arbitrary_multisig_script() -> ScriptBuf {
245296 . push_opcode ( OP_CHECKMULTISIG )
246297 . into_script ( )
247298}
299+ fn arbitrary_p2wpkh_script ( ) -> ScriptBuf {
300+ let pubkey = arbitrary_pubkey ( ) ;
301+ let pubkey_hash = hash160:: Hash :: hash ( & pubkey. to_bytes ( ) ) ;
302+
303+ // P2WPKH: 0 <20-byte pubkey hash>
304+ Builder :: new ( )
305+ . push_int ( 0 )
306+ . push_slice ( pubkey_hash. as_byte_array ( ) )
307+ . into_script ( )
308+ }
309+
310+ fn arbitrary_p2wsh_script ( ) -> ScriptBuf {
311+ let redeem_script = arbitrary_multisig_script ( ) ; // any witness script
312+ let script_hash = sha256:: Hash :: hash ( redeem_script. as_bytes ( ) ) ;
313+
314+ // P2WSH: 0 <32-byte script hash>
315+ Builder :: new ( )
316+ . push_int ( 0 )
317+ . push_slice ( script_hash. as_byte_array ( ) )
318+ . into_script ( )
319+ }
320+
321+ fn arbitrary_p2tr_script ( ) -> ScriptBuf {
322+ let secp = Secp256k1 :: new ( ) ;
323+ let sk = secp256k1:: SecretKey :: from_slice ( & [ 2u8 ; 32 ] ) . unwrap ( ) ;
324+ let internal_key = secp256k1:: PublicKey :: from_secret_key ( & secp, & sk) ;
325+ let x_only = XOnlyPublicKey :: from ( internal_key) ;
326+
327+ // Taproot output script: OP_1 <x-only pubkey>
328+ Builder :: new ( )
329+ . push_int ( 1 )
330+ . push_slice ( & x_only. serialize ( ) )
331+ . into_script ( )
332+ }
333+
334+ #[ test]
335+ fn raw_transactions__decode_script_segwit__modelled ( ) {
336+
337+ let node = Node :: with_wallet ( Wallet :: Default , & [ "-txindex" ] ) ;
338+ node. client . load_wallet ( "default" ) . ok ( ) ; // Ensure wallet is loaded
339+ node. fund_wallet ( ) ;
340+
341+ // Get a new address and script
342+ let address_unc = node
343+ . client
344+ . get_new_address ( None , None )
345+ . expect ( "getnewaddress" )
346+ . address ( )
347+ . expect ( "valid address string" ) ;
348+
349+ let address = address_unc
350+ . require_network ( Network :: Regtest )
351+ . expect ( "must be regtest" ) ;
352+
353+ let script = address. script_pubkey ( ) ;
354+ let hex = script. to_hex_string ( ) ;
355+
356+ // Decode script
357+ let json = node. client . decode_script ( & hex) . expect ( "decodescript" ) ;
358+ let model: Result < mtype:: DecodeScript , DecodeScriptError > = json. into_model ( ) ;
359+ let decoded = model. expect ( "DecodeScript into model" ) ;
360+
361+ // Extract the type field
362+ let script_type = decoded
363+ . segwit
364+ . as_ref ( )
365+ . map ( |s| s. type_ . as_str ( ) )
366+ . unwrap_or_else ( || decoded. type_ . as_str ( ) ) ;
367+
368+ assert_eq ! (
369+ script_type,
370+ "witness_v0_keyhash" ,
371+ "Expected script type to be witness_v0_keyhash"
372+ ) ;
373+
374+ // Compare hex from segwit
375+ let decoded_hex = decoded
376+ . segwit
377+ . as_ref ( )
378+ . map ( |s| & s. hex )
379+ . unwrap_or_else ( || {
380+ panic ! ( "Expected segwit hex to be present" )
381+ } ) ;
382+
383+ assert_eq ! ( * decoded_hex, script, "Script hex does not match" ) ;
384+
385+ // Compare addresses from segwit or fallback
386+ let address_unc_check = address. into_unchecked ( ) ;
387+ let segwit_addresses = decoded
388+ . segwit
389+ . as_ref ( )
390+ . map ( |s| & s. addresses )
391+ . unwrap_or ( & decoded. addresses ) ;
392+
393+ assert ! (
394+ segwit_addresses. iter( ) . any( |a| a == & address_unc_check) ,
395+ "Expected address {:?} in segwit.addresses or top-level addresses: {:?}" ,
396+ address_unc_check,
397+ segwit_addresses
398+ ) ;
399+ }
248400
249401#[ test]
250402#[ cfg( feature = "TODO" ) ]
0 commit comments