44
55#![ allow( non_snake_case) ] // Test names intentionally use double underscore.
66#![ allow( unused_imports) ] // Because of feature gated tests.
7-
7+ use bitcoin :: address :: NetworkUnchecked ;
88use bitcoin:: consensus:: encode;
9+ use bitcoin:: hashes:: { hash160, sha256, Hash } ;
910use bitcoin:: hex:: FromHex as _;
11+ use bitcoin:: key:: { Secp256k1 , XOnlyPublicKey } ;
1012use bitcoin:: opcodes:: all:: * ;
13+ use bitcoin:: script:: Builder ;
1114use bitcoin:: {
12- absolute, consensus, hex, psbt, script, transaction, Amount , ScriptBuf , Transaction , TxOut ,
15+ absolute, consensus, hex, psbt, script, secp256k1, transaction, Address , Amount , Network ,
16+ PublicKey , ScriptBuf , Transaction , TxOut , WPubkeyHash , WScriptHash ,
1317} ;
1418use integration_test:: { Node , NodeExt as _, Wallet } ;
1519use node:: vtype:: * ;
1620use node:: { mtype, Input , Output } ; // All the version specific types.
21+ use rand:: Rng ;
1722
1823#[ test]
1924#[ cfg( not( feature = "v17" ) ) ] // analyzepsbt was added in v0.18.
@@ -196,24 +201,72 @@ fn raw_transactions__decode_raw_transaction__modelled() {
196201}
197202
198203#[ test]
199- // FIXME: Seems the returned fields are different depending on the script. Needs more thorough testing.
204+ // FIXME: Bitcoin Core may populate different fields depending on
205+ // the script type and Core version (e.g. legacy vs segwit vs taproot).
200206fn raw_transactions__decode_script__modelled ( ) {
201207 let node = Node :: with_wallet ( Wallet :: Default , & [ "-txindex" ] ) ;
202208 node. fund_wallet ( ) ;
203209
204- let p2pkh = arbitrary_p2pkh_script ( ) ;
205- let multi = arbitrary_multisig_script ( ) ;
206-
207- for script in & [ p2pkh, multi] {
208- let hex = script. to_hex_string ( ) ;
209-
210- let json: DecodeScript = node. client . decode_script ( & hex) . expect ( "decodescript" ) ;
211- let model: Result < mtype:: DecodeScript , DecodeScriptError > = json. into_model ( ) ;
212- model. unwrap ( ) ;
210+ let cases = [
211+ ( "p2pkh" , arbitrary_p2pkh_script ( ) , Some ( "pubkeyhash" ) ) ,
212+ ( "multisig" , arbitrary_multisig_script ( ) , Some ( "multisig" ) ) ,
213+ ( "p2sh" , arbitrary_p2sh_script ( ) , Some ( "scripthash" ) ) ,
214+ ( "bare" , arbitrary_bare_script ( ) , Some ( "nonstandard" ) ) ,
215+ ( "p2wpkh" , arbitrary_p2wpkh_script ( ) , Some ( "witness_v0_keyhash" ) ) ,
216+ ( "p2wsh" , arbitrary_p2wsh_script ( ) , Some ( "witness_v0_scripthash" ) ) ,
217+ ( "p2tr" , arbitrary_p2tr_script ( ) , Some ( "witness_v1_taproot" ) ) ,
218+ ] ;
219+
220+ for ( label, script, expected_type) in cases {
221+ // The input is provided as raw script hex, not an address.
222+ let json: DecodeScript =
223+ node. client . decode_script ( & script. to_hex_string ( ) )
224+ . expect ( "decodescript" ) ;
225+
226+ // Convert the RPC response into the model type.
227+ // This step normalizes Core’s JSON into a structured representation.
228+ let decoded = json
229+ . into_model ( )
230+ . expect ( "DecodeScript into model" ) ;
231+
232+ // Verify that Core identifies the script type as expected.
233+ // Some scripts may legitimately omit type information depending on Core behavior.
234+ if let Some ( expected) = expected_type {
235+ assert_eq ! (
236+ decoded. type_, expected,
237+ "Unexpected script type for {label}"
238+ ) ;
239+ }
240+
241+ // For standard scripts, Core should provide at least one resolved address.
242+ // Core may populate either `address` or `addresses`, depending on script class.
243+ if expected_type != Some ( "nonstandard" ) {
244+ assert ! (
245+ !decoded. addresses. is_empty( ) || decoded. address. is_some( ) ,
246+ "Expected at least one address for {label}"
247+ ) ;
248+ }
213249 }
214250}
251+ fn arbitrary_p2sh_script ( ) -> ScriptBuf {
252+ let redeem_script = arbitrary_multisig_script ( ) ; // or arbitrary_p2pkh_script()
253+ let redeem_script_hash = hash160:: Hash :: hash ( redeem_script. as_bytes ( ) ) ;
254+
255+ script:: Builder :: new ( )
256+ . push_opcode ( bitcoin:: opcodes:: all:: OP_HASH160 )
257+ . push_slice ( redeem_script_hash. as_byte_array ( ) )
258+ . push_opcode ( bitcoin:: opcodes:: all:: OP_EQUAL )
259+ . into_script ( )
260+ }
261+ fn arbitrary_bare_script ( ) -> ScriptBuf {
262+ script:: Builder :: new ( ) . push_opcode ( OP_RETURN ) . push_slice ( b"hello" ) . 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+ }
215269
216- // Script builder code copied from rust-bitcoin script unit tests.
217270fn arbitrary_p2pkh_script ( ) -> ScriptBuf {
218271 let pubkey_hash = <[ u8 ; 20 ] >:: from_hex ( "16e1ae70ff0fa102905d4af297f6912bda6cce19" ) . unwrap ( ) ;
219272
@@ -225,7 +278,6 @@ fn arbitrary_p2pkh_script() -> ScriptBuf {
225278 . push_opcode ( OP_CHECKSIG )
226279 . into_script ( )
227280}
228-
229281fn arbitrary_multisig_script ( ) -> ScriptBuf {
230282 let pk1 =
231283 <[ u8 ; 33 ] >:: from_hex ( "022afc20bf379bc96a2f4e9e63ffceb8652b2b6a097f63fbee6ecec2a49a48010e" )
@@ -244,6 +296,96 @@ fn arbitrary_multisig_script() -> ScriptBuf {
244296 . push_opcode ( OP_CHECKMULTISIG )
245297 . into_script ( )
246298}
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 ( ) . push_int ( 0 ) . push_slice ( pubkey_hash. as_byte_array ( ) ) . into_script ( )
305+ }
306+
307+ fn arbitrary_p2wsh_script ( ) -> ScriptBuf {
308+ let redeem_script = arbitrary_multisig_script ( ) ; // any witness script
309+ let script_hash = sha256:: Hash :: hash ( redeem_script. as_bytes ( ) ) ;
310+
311+ // P2WSH: 0 <32-byte script hash>
312+ Builder :: new ( ) . push_int ( 0 ) . push_slice ( script_hash. as_byte_array ( ) ) . into_script ( )
313+ }
314+
315+ fn arbitrary_p2tr_script ( ) -> ScriptBuf {
316+ let secp = Secp256k1 :: new ( ) ;
317+ let sk = secp256k1:: SecretKey :: from_slice ( & [ 2u8 ; 32 ] ) . unwrap ( ) ;
318+ let internal_key = secp256k1:: PublicKey :: from_secret_key ( & secp, & sk) ;
319+ let x_only = XOnlyPublicKey :: from ( internal_key) ;
320+
321+ // Taproot output script: OP_1 <x-only pubkey>
322+ Builder :: new ( ) . push_int ( 1 ) . push_slice ( & x_only. serialize ( ) ) . into_script ( )
323+ }
324+
325+ #[ test]
326+ fn raw_transactions__decode_script_segwit__modelled ( ) {
327+ let node = Node :: with_wallet ( Wallet :: Default , & [ "-txindex" ] ) ;
328+ node. client . load_wallet ( "default" ) . ok ( ) ;
329+ node. fund_wallet ( ) ;
330+
331+ let address = node
332+ . client
333+ . get_new_address ( None , None )
334+ . expect ( "getnewaddress" )
335+ . address ( )
336+ . expect ( "valid address" )
337+ . require_network ( Network :: Regtest )
338+ . expect ( "regtest" ) ;
339+
340+ // Convert the address into its locking script (scriptPubKey).
341+ // We assert on the script itself (not the address encoding) to ensure
342+ // we are testing actual SegWit script semantics.
343+ let spk = address. script_pubkey ( ) ;
344+ assert ! (
345+ spk. is_witness_program( ) ,
346+ "Expected segwit script"
347+ ) ;
348+
349+ // Decode the script and convert it into the model type.
350+ // Core may populate fields differently depending on script type and version.
351+ let decoded = node
352+ . client
353+ . decode_script ( & spk. to_hex_string ( ) )
354+ . expect ( "decodescript" )
355+ . into_model ( )
356+ . expect ( "DecodeScript into model" ) ;
357+
358+ // For SegWit scripts, Core should populate the `segwit` sub-object.
359+ let segwit = decoded
360+ . segwit
361+ . as_ref ( )
362+ . expect ( "Expected segwit field" ) ;
363+
364+ // The decoded SegWit script hex must match the original scriptPubKey.
365+ assert_eq ! ( segwit. hex, spk) ;
366+
367+ // Verify that Core correctly identifies the SegWit version and script type.
368+ // For a wallet-generated address on regtest, this should be v0 P2WPKH.
369+ assert_eq ! (
370+ segwit. type_. as_str( ) ,
371+ "witness_v0_keyhash" ,
372+ "Unexpected segwit script type"
373+ ) ;
374+
375+ // Core returns addresses without network information.
376+ // We compare against the unchecked form of the address for correctness.
377+ let addr_unc = address. into_unchecked ( ) ;
378+ let addresses = & segwit. addresses ;
379+
380+ // Ensure the decoded SegWit script resolves back to the original address.
381+ assert ! (
382+ addresses. contains( & addr_unc) ,
383+ "Expected address {:?} in segwit.addresses: {:?}" ,
384+ addr_unc,
385+ addresses
386+ ) ;
387+ }
388+
247389
248390#[ test]
249391fn raw_transactions__finalize_psbt__modelled ( ) {
0 commit comments