wallet_signrawtransactionwithwallet.py raw

   1  #!/usr/bin/env python3
   2  # Copyright (c) 2015-present The Bitcoin Core developers
   3  # Distributed under the MIT software license, see the accompanying
   4  # file COPYING or http://www.opensource.org/licenses/mit-license.php.
   5  """Test transaction signing using the signrawtransactionwithwallet RPC."""
   6  
   7  from test_framework.blocktools import (
   8      COINBASE_MATURITY,
   9  )
  10  from test_framework.address import (
  11      script_to_p2wsh,
  12  )
  13  from test_framework.test_framework import BitcoinTestFramework
  14  from test_framework.util import (
  15      assert_equal,
  16      assert_raises_rpc_error,
  17  )
  18  from test_framework.messages import (
  19      CTxInWitness,
  20      tx_from_hex,
  21  )
  22  from test_framework.script import (
  23      CScript,
  24      OP_CHECKLOCKTIMEVERIFY,
  25      OP_CHECKSEQUENCEVERIFY,
  26      OP_DROP,
  27      OP_TRUE,
  28  )
  29  
  30  from decimal import (
  31      Decimal,
  32      getcontext,
  33  )
  34  
  35  
  36  RAW_TX = '020000000156b958f78e3f24e0b2f4e4db1255426b0902027cb37e3ddadb52e37c3557dddb0000000000ffffffff01c0a6b929010000001600149a2ee8c77140a053f36018ac8124a6ececc1668a00000000'
  37  
  38  
  39  class SignRawTransactionWithWalletTest(BitcoinTestFramework):
  40      def set_test_params(self):
  41          self.setup_clean_chain = True
  42          self.num_nodes = 2
  43  
  44      def skip_test_if_missing_module(self):
  45          self.skip_if_no_wallet()
  46  
  47      def test_with_lock_outputs(self):
  48          self.log.info("Test correct error reporting when trying to sign a locked output")
  49          self.nodes[0].encryptwallet("password")
  50          assert_raises_rpc_error(-13, "Please enter the wallet passphrase with walletpassphrase first", self.nodes[0].signrawtransactionwithwallet, RAW_TX)
  51          self.nodes[0].walletpassphrase("password", 9999)
  52  
  53      def test_with_invalid_sighashtype(self):
  54          self.log.info("Test signrawtransactionwithwallet raises if an invalid sighashtype is passed")
  55          assert_raises_rpc_error(-8, "'all' is not a valid sighash parameter.", self.nodes[0].signrawtransactionwithwallet, hexstring=RAW_TX, sighashtype="all")
  56  
  57      def script_verification_error_test(self):
  58          """Create and sign a raw transaction with valid (vin 0), invalid (vin 1) and one missing (vin 2) input script.
  59  
  60          Expected results:
  61  
  62          3) The transaction has no complete set of signatures
  63          4) Two script verification errors occurred
  64          5) Script verification errors have certain properties ("txid", "vout", "scriptSig", "sequence", "error")
  65          6) The verification errors refer to the invalid (vin 1) and missing input (vin 2)"""
  66          self.log.info("Test script verification errors")
  67          privKeys = ['cUeKHd5orzT3mz8P9pxyREHfsWtVfgsfDjiZZBcjUBAaGk1BTj7N']
  68  
  69          inputs = [
  70              # Valid pay-to-pubkey script
  71              {'txid': '9b907ef1e3c26fc71fe4a4b3580bc75264112f95050014157059c736f0202e71', 'vout': 0},
  72              # Invalid script
  73              {'txid': '5b8673686910442c644b1f4993d8f7753c7c8fcb5c87ee40d56eaeef25204547', 'vout': 7},
  74              # Missing scriptPubKey
  75              {'txid': '9b907ef1e3c26fc71fe4a4b3580bc75264112f95050014157059c736f0202e71', 'vout': 1},
  76          ]
  77  
  78          scripts = [
  79              # Valid pay-to-pubkey script
  80              {'txid': '9b907ef1e3c26fc71fe4a4b3580bc75264112f95050014157059c736f0202e71', 'vout': 0,
  81               'scriptPubKey': '76a91460baa0f494b38ce3c940dea67f3804dc52d1fb9488ac'},
  82              # Invalid script
  83              {'txid': '5b8673686910442c644b1f4993d8f7753c7c8fcb5c87ee40d56eaeef25204547', 'vout': 7,
  84               'scriptPubKey': 'badbadbadbad'}
  85          ]
  86  
  87          outputs = {'mpLQjfK79b7CCV4VMJWEWAj5Mpx8Up5zxB': 0.1}
  88  
  89          rawTx = self.nodes[0].createrawtransaction(inputs, outputs)
  90  
  91          # Make sure decoderawtransaction is at least marginally sane
  92          decodedRawTx = self.nodes[0].decoderawtransaction(rawTx)
  93          for i, inp in enumerate(inputs):
  94              assert_equal(decodedRawTx["vin"][i]["txid"], inp["txid"])
  95              assert_equal(decodedRawTx["vin"][i]["vout"], inp["vout"])
  96  
  97          # Make sure decoderawtransaction throws if there is extra data
  98          assert_raises_rpc_error(-22, "TX decode failed", self.nodes[0].decoderawtransaction, rawTx + "00")
  99  
 100          rawTxSigned = self.nodes[0].signrawtransactionwithkey(rawTx, privKeys, scripts)
 101  
 102          # 3) The transaction has no complete set of signatures
 103          assert not rawTxSigned['complete']
 104  
 105          # 4) Two script verification errors occurred
 106          assert 'errors' in rawTxSigned
 107          assert_equal(len(rawTxSigned['errors']), 2)
 108  
 109          # 5) Script verification errors have certain properties
 110          assert 'txid' in rawTxSigned['errors'][0]
 111          assert 'vout' in rawTxSigned['errors'][0]
 112          assert 'witness' in rawTxSigned['errors'][0]
 113          assert 'scriptSig' in rawTxSigned['errors'][0]
 114          assert 'sequence' in rawTxSigned['errors'][0]
 115          assert 'error' in rawTxSigned['errors'][0]
 116  
 117          # 6) The verification errors refer to the invalid (vin 1) and missing input (vin 2)
 118          assert_equal(rawTxSigned['errors'][0]['txid'], inputs[1]['txid'])
 119          assert_equal(rawTxSigned['errors'][0]['vout'], inputs[1]['vout'])
 120          assert_equal(rawTxSigned['errors'][1]['txid'], inputs[2]['txid'])
 121          assert_equal(rawTxSigned['errors'][1]['vout'], inputs[2]['vout'])
 122          assert not rawTxSigned['errors'][0]['witness']
 123  
 124          # Now test signing failure for transaction with input witnesses
 125          p2wpkh_raw_tx = "01000000000102fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f00000000494830450221008b9d1dc26ba6a9cb62127b02742fa9d754cd3bebf337f7a55d114c8e5cdd30be022040529b194ba3f9281a99f2b1c0a19c0489bc22ede944ccf4ecbab4cc618ef3ed01eeffffffef51e1b804cc89d182d279655c3aa89e815b1b309fe287d9b2b55d57b90ec68a0100000000ffffffff02202cb206000000001976a9148280b37df378db99f66f85c95a783a76ac7a6d5988ac9093510d000000001976a9143bde42dbee7e4dbe6a21b2d50ce2f0167faa815988ac000247304402203609e17b84f6a7d30c80bfa610b5b4542f32a8a0d5447a12fb1366d7f01cc44a0220573a954c4518331561406f90300e8f3358f51928d43c212a8caed02de67eebee0121025476c2e83188368da1ff3e292e7acafcdb3566bb0ad253f62fc70f07aeee635711000000"
 126  
 127          rawTxSigned = self.nodes[0].signrawtransactionwithwallet(p2wpkh_raw_tx)
 128  
 129          # 7) The transaction has no complete set of signatures
 130          assert not rawTxSigned['complete']
 131  
 132          # 8) Two script verification errors occurred
 133          assert 'errors' in rawTxSigned
 134          assert_equal(len(rawTxSigned['errors']), 2)
 135  
 136          # 9) Script verification errors have certain properties
 137          assert 'txid' in rawTxSigned['errors'][0]
 138          assert 'vout' in rawTxSigned['errors'][0]
 139          assert 'witness' in rawTxSigned['errors'][0]
 140          assert 'scriptSig' in rawTxSigned['errors'][0]
 141          assert 'sequence' in rawTxSigned['errors'][0]
 142          assert 'error' in rawTxSigned['errors'][0]
 143  
 144          # Non-empty witness checked here
 145          assert_equal(rawTxSigned['errors'][1]['witness'], ["304402203609e17b84f6a7d30c80bfa610b5b4542f32a8a0d5447a12fb1366d7f01cc44a0220573a954c4518331561406f90300e8f3358f51928d43c212a8caed02de67eebee01", "025476c2e83188368da1ff3e292e7acafcdb3566bb0ad253f62fc70f07aeee6357"])
 146          assert not rawTxSigned['errors'][0]['witness']
 147  
 148      def test_fully_signed_tx(self):
 149          self.log.info("Test signing a fully signed transaction does nothing")
 150          self.nodes[0].walletpassphrase("password", 9999)
 151          self.generate(self.nodes[0], COINBASE_MATURITY + 1)
 152          rawtx = self.nodes[0].createrawtransaction([], [{self.nodes[0].getnewaddress(): 10}])
 153          fundedtx = self.nodes[0].fundrawtransaction(rawtx)
 154          signedtx = self.nodes[0].signrawtransactionwithwallet(fundedtx["hex"])
 155          assert_equal(signedtx["complete"], True)
 156          signedtx2 = self.nodes[0].signrawtransactionwithwallet(signedtx["hex"])
 157          assert_equal(signedtx2["complete"], True)
 158          assert_equal(signedtx["hex"], signedtx2["hex"])
 159          self.nodes[0].walletlock()
 160  
 161      def OP_1NEGATE_test(self):
 162          self.log.info("Test OP_1NEGATE (0x4f) satisfies BIP62 minimal push standardness rule")
 163          hex_str = (
 164              "0200000001FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"
 165              "FFFFFFFF00000000044F024F9CFDFFFFFF01F0B9F5050000000023210277777777"
 166              "77777777777777777777777777777777777777777777777777777777AC66030000"
 167          )
 168          prev_txs = [
 169              {
 170                  "txid": "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF",
 171                  "vout": 0,
 172                  "scriptPubKey": "A914AE44AB6E9AA0B71F1CD2B453B69340E9BFBAEF6087",
 173                  "redeemScript": "4F9C",
 174                  "amount": 1,
 175              }
 176          ]
 177          txn = self.nodes[0].signrawtransactionwithwallet(hex_str, prev_txs)
 178          assert txn["complete"]
 179  
 180      def test_signing_with_csv(self):
 181          self.log.info("Test signing a transaction containing a fully signed CSV input")
 182          self.nodes[0].walletpassphrase("password", 9999)
 183          getcontext().prec = 8
 184  
 185          # Make sure CSV is active
 186          assert self.nodes[0].getdeploymentinfo()['deployments']['csv']['active']
 187  
 188          # Create a P2WSH script with CSV
 189          script = CScript([1, OP_CHECKSEQUENCEVERIFY, OP_DROP])
 190          address = script_to_p2wsh(script)
 191  
 192          # Fund that address and make the spend
 193          utxo1 = self.create_outpoints(self.nodes[0], outputs=[{address: 1}])[0]
 194          self.generate(self.nodes[0], 1)
 195          utxo2 = self.nodes[0].listunspent()[0]
 196          amt = Decimal(1) + utxo2["amount"] - Decimal(0.00001)
 197          tx = self.nodes[0].createrawtransaction(
 198              [{**utxo1, "sequence": 1},{"txid": utxo2["txid"], "vout": utxo2["vout"]}],
 199              [{self.nodes[0].getnewaddress(): amt}],
 200              self.nodes[0].getblockcount()
 201          )
 202  
 203          # Set the witness script
 204          ctx = tx_from_hex(tx)
 205          ctx.wit.vtxinwit.append(CTxInWitness())
 206          ctx.wit.vtxinwit[0].scriptWitness.stack = [CScript([OP_TRUE]), script]
 207          tx = ctx.serialize_with_witness().hex()
 208  
 209          # Sign and send the transaction
 210          signed = self.nodes[0].signrawtransactionwithwallet(tx)
 211          assert_equal(signed["complete"], True)
 212          self.nodes[0].sendrawtransaction(signed["hex"])
 213  
 214      def test_signing_with_cltv(self):
 215          self.log.info("Test signing a transaction containing a fully signed CLTV input")
 216          self.nodes[0].walletpassphrase("password", 9999)
 217          getcontext().prec = 8
 218  
 219          # Make sure CLTV is active
 220          assert self.nodes[0].getdeploymentinfo()['deployments']['bip65']['active']
 221  
 222          # Create a P2WSH script with CLTV
 223          script = CScript([100, OP_CHECKLOCKTIMEVERIFY, OP_DROP])
 224          address = script_to_p2wsh(script)
 225  
 226          # Fund that address and make the spend
 227          utxo1 = self.create_outpoints(self.nodes[0], outputs=[{address: 1}])[0]
 228          self.generate(self.nodes[0], 1)
 229          utxo2 = self.nodes[0].listunspent()[0]
 230          amt = Decimal(1) + utxo2["amount"] - Decimal(0.00001)
 231          tx = self.nodes[0].createrawtransaction(
 232              [utxo1, {"txid": utxo2["txid"], "vout": utxo2["vout"]}],
 233              [{self.nodes[0].getnewaddress(): amt}],
 234              self.nodes[0].getblockcount()
 235          )
 236  
 237          # Set the witness script
 238          ctx = tx_from_hex(tx)
 239          ctx.wit.vtxinwit.append(CTxInWitness())
 240          ctx.wit.vtxinwit[0].scriptWitness.stack = [CScript([OP_TRUE]), script]
 241          tx = ctx.serialize_with_witness().hex()
 242  
 243          # Sign and send the transaction
 244          signed = self.nodes[0].signrawtransactionwithwallet(tx)
 245          assert_equal(signed["complete"], True)
 246          self.nodes[0].sendrawtransaction(signed["hex"])
 247  
 248      def test_signing_with_missing_prevtx_info(self):
 249          txid = "1d1d4e24ed99057e84c3f80fd8fbec79ed9e1acee37da269356ecea000000000"
 250          for type in ["bech32", "p2sh-segwit", "legacy"]:
 251              self.log.info(f"Test signing with missing prevtx info ({type})")
 252              addr = self.nodes[0].getnewaddress("", type)
 253              addrinfo = self.nodes[0].getaddressinfo(addr)
 254              pubkey = addrinfo["scriptPubKey"]
 255              inputs = [{'txid': txid, 'vout': 3, 'sequence': 1000}]
 256              outputs = {self.nodes[0].getnewaddress(): 1}
 257              rawtx = self.nodes[0].createrawtransaction(inputs, outputs)
 258  
 259              prevtx = dict(txid=txid, scriptPubKey=pubkey, vout=3, amount=1)
 260              succ = self.nodes[0].signrawtransactionwithwallet(rawtx, [prevtx])
 261              assert succ["complete"]
 262  
 263              if type == "legacy":
 264                  del prevtx["amount"]
 265                  succ = self.nodes[0].signrawtransactionwithwallet(rawtx, [prevtx])
 266                  assert succ["complete"]
 267              else:
 268                  assert_raises_rpc_error(-3, "Missing amount", self.nodes[0].signrawtransactionwithwallet, rawtx, [
 269                      {
 270                          "txid": txid,
 271                          "scriptPubKey": pubkey,
 272                          "vout": 3,
 273                      }
 274                  ])
 275  
 276              assert_raises_rpc_error(-3, "Missing vout", self.nodes[0].signrawtransactionwithwallet, rawtx, [
 277                  {
 278                      "txid": txid,
 279                      "scriptPubKey": pubkey,
 280                      "amount": 1,
 281                  }
 282              ])
 283              assert_raises_rpc_error(-3, "Missing txid", self.nodes[0].signrawtransactionwithwallet, rawtx, [
 284                  {
 285                      "scriptPubKey": pubkey,
 286                      "vout": 3,
 287                      "amount": 1,
 288                  }
 289              ])
 290              assert_raises_rpc_error(-3, "Missing scriptPubKey", self.nodes[0].signrawtransactionwithwallet, rawtx, [
 291                  {
 292                      "txid": txid,
 293                      "vout": 3,
 294                      "amount": 1
 295                  }
 296              ])
 297  
 298      def run_test(self):
 299          self.script_verification_error_test()
 300          self.OP_1NEGATE_test()
 301          self.test_with_lock_outputs()
 302          self.test_with_invalid_sighashtype()
 303          self.test_fully_signed_tx()
 304          self.test_signing_with_csv()
 305          self.test_signing_with_cltv()
 306          self.test_signing_with_missing_prevtx_info()
 307  
 308  
 309  if __name__ == '__main__':
 310      SignRawTransactionWithWalletTest(__file__).main()
 311