completed.py 3.2 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586
  1. #!/usr/bin/env python3
  2. #
  3. # mmgen = Multi-Mode GENerator, a command-line cryptocurrency wallet
  4. # Copyright (C)2013-2022 The MMGen Project <mmgen@tuta.io>
  5. # Licensed under the GNU General Public License, Version 3:
  6. # https://www.gnu.org/licenses
  7. # Public project repositories:
  8. # https://github.com/mmgen/mmgen
  9. # https://gitlab.com/mmgen/mmgen
  10. """
  11. proto.btc.tx.completed: Bitcoin completed transaction class
  12. """
  13. import mmgen.tx.completed as TxBase
  14. from .base import Base,scriptPubKey2addr
  15. from ....obj import HexStr
  16. from ....util import msg
  17. class Completed(Base,TxBase.Completed):
  18. fn_fee_unit = 'satoshi'
  19. # check signature and witness data
  20. def check_sigs(self): # return True if sigs found, False otherwise; raise exception on error
  21. txins = self.deserialized.txins
  22. has_ss = any(ti['scriptSig'] for ti in txins)
  23. has_witness = any('witness' in ti and ti['witness'] for ti in txins)
  24. if not (has_ss or has_witness):
  25. return False
  26. fs = "Hex TX has {} scriptSig but input is of type '{}'!"
  27. for n in range(len(txins)):
  28. ti,mmti = txins[n],self.inputs[n]
  29. if ti['scriptSig'] == '' or ( len(ti['scriptSig']) == 46 and # native P2WPKH or P2SH-P2WPKH
  30. ti['scriptSig'][:6] == '16' + self.proto.witness_vernum_hex + '14' ):
  31. assert 'witness' in ti, 'missing witness'
  32. assert type(ti['witness']) == list and len(ti['witness']) == 2, 'malformed witness'
  33. assert len(ti['witness'][1]) == 66, 'incorrect witness pubkey length'
  34. assert mmti.mmtype == ('S','B')[ti['scriptSig']==''], fs.format('witness-type',mmti.mmtype)
  35. else: # non-witness
  36. assert mmti.mmtype not in ('S','B'), fs.format('signature in',mmti.mmtype)
  37. assert not 'witness' in ti, 'non-witness input has witness'
  38. # sig_size 72 (DER format), pubkey_size 'compressed':33, 'uncompressed':65
  39. assert (200 < len(ti['scriptSig']) < 300), 'malformed scriptSig' # VERY rough check
  40. return True
  41. def check_pubkey_scripts(self):
  42. for n,i in enumerate(self.inputs,1):
  43. addr,fmt = scriptPubKey2addr(self.proto,i.scriptPubKey)
  44. if i.addr != addr:
  45. if fmt != i.addr.addr_fmt:
  46. m = 'Address format of scriptPubKey ({}) does not match that of address ({}) in input #{}'
  47. msg(m.format(fmt,i.addr.addr_fmt,n))
  48. m = 'ERROR: Address and scriptPubKey of transaction input #{} do not match!'
  49. die(3,(m+'\n {:23}{}'*3).format(n, 'address:',i.addr,
  50. 'scriptPubKey:',i.scriptPubKey,
  51. 'scriptPubKey->address:',addr ))
  52. # def is_replaceable_from_rpc(self):
  53. # dec_tx = await self.rpc.call('decoderawtransaction',self.serialized)
  54. # return None < dec_tx['vin'][0]['sequence'] <= self.proto.max_int - 2
  55. def is_replaceable(self):
  56. return self.inputs[0].sequence == self.proto.max_int - 2
  57. @property
  58. def send_amt(self):
  59. return self.sum_outputs(
  60. exclude = None if len(self.outputs) == 1 else self.get_chg_output_idx()
  61. )
  62. def check_txfile_hex_data(self):
  63. self.serialized = HexStr(self.serialized)
  64. def parse_txfile_serialized_data(self):
  65. pass
  66. @property
  67. def fee(self):
  68. return self.sum_inputs() - self.sum_outputs()
  69. @property
  70. def change(self):
  71. return self.sum_outputs() - self.send_amt
  72. def get_serialized_locktime(self):
  73. return int(bytes.fromhex(self.serialized[-8:])[::-1].hex(),16)