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