base.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325
  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. base_proto.bitcoin.tx.base: Bitcoin base transaction class
  12. """
  13. from collections import namedtuple
  14. import mmgen.tx.base as TxBase
  15. from ....opts import opt
  16. from ....obj import MMGenObject,MMGenList,HexStr
  17. from ....util import msg,dmsg,make_chksum_6
  18. def addr2pubhash(proto,addr):
  19. ap = proto.parse_addr(addr)
  20. assert ap,f'coin address {addr!r} could not be parsed'
  21. return ap.bytes.hex()
  22. def addr2scriptPubKey(proto,addr):
  23. return {
  24. 'p2pkh': '76a914' + addr2pubhash(proto,addr) + '88ac',
  25. 'p2sh': 'a914' + addr2pubhash(proto,addr) + '87',
  26. 'bech32': proto.witness_vernum_hex + '14' + addr2pubhash(proto,addr)
  27. }[addr.addr_fmt]
  28. def scriptPubKey2addr(proto,s):
  29. if len(s) == 50 and s[:6] == '76a914' and s[-4:] == '88ac':
  30. return proto.pubhash2addr(bytes.fromhex(s[6:-4]),p2sh=False),'p2pkh'
  31. elif len(s) == 46 and s[:4] == 'a914' and s[-2:] == '87':
  32. return proto.pubhash2addr(bytes.fromhex(s[4:-2]),p2sh=True),'p2sh'
  33. elif len(s) == 44 and s[:4] == proto.witness_vernum_hex + '14':
  34. return proto.pubhash2bech32addr(bytes.fromhex(s[4:])),'bech32'
  35. else:
  36. raise NotImplementedError(f'Unknown scriptPubKey ({s})')
  37. def DeserializeTX(proto,txhex):
  38. """
  39. Parse a serialized Bitcoin transaction
  40. For checking purposes, additionally reconstructs the serialized TX without signature
  41. """
  42. def bytes2int(bytes_le):
  43. if bytes_le[-1] & 0x80: # sign bit is set
  44. die(3,"{}: Negative values not permitted in transaction!".format(bytes_le[::-1].hex()))
  45. return int(bytes_le[::-1].hex(),16)
  46. def bytes2coin_amt(bytes_le):
  47. return proto.coin_amt(bytes2int(bytes_le) * proto.coin_amt.satoshi)
  48. def bshift(n,skip=False,sub_null=False):
  49. ret = tx[var.idx:var.idx+n]
  50. var.idx += n
  51. if sub_null:
  52. var.raw_tx += b'\x00'
  53. elif not skip:
  54. var.raw_tx += ret
  55. return ret
  56. # https://bitcoin.org/en/developer-reference#compactsize-unsigned-integers
  57. # For example, the number 515 is encoded as 0xfd0302.
  58. def readVInt(skip=False):
  59. s = tx[var.idx]
  60. var.idx += 1
  61. if not skip:
  62. var.raw_tx.append(s)
  63. vbytes_len = 1 if s < 0xfd else 2 if s == 0xfd else 4 if s == 0xfe else 8
  64. if vbytes_len == 1:
  65. return s
  66. else:
  67. vbytes = tx[var.idx:var.idx+vbytes_len]
  68. var.idx += vbytes_len
  69. if not skip:
  70. var.raw_tx += vbytes
  71. return int(vbytes[::-1].hex(),16)
  72. def make_txid(tx_bytes):
  73. from hashlib import sha256
  74. return sha256(sha256(tx_bytes).digest()).digest()[::-1].hex()
  75. class vardata:
  76. idx = 0
  77. raw_tx = bytearray()
  78. var = vardata()
  79. tx = bytes.fromhex(txhex)
  80. d = { 'version': bytes2int(bshift(4)) }
  81. has_witness = tx[var.idx] == 0
  82. if has_witness:
  83. u = bshift(2,skip=True).hex()
  84. if u != '0001':
  85. raise IllegalWitnessFlagValue(f'{u!r}: Illegal value for flag in transaction!')
  86. d['num_txins'] = readVInt()
  87. d['txins'] = MMGenList([{
  88. 'txid': bshift(32)[::-1].hex(),
  89. 'vout': bytes2int(bshift(4)),
  90. 'scriptSig': bshift(readVInt(skip=True),sub_null=True).hex(),
  91. 'nSeq': bshift(4)[::-1].hex()
  92. } for i in range(d['num_txins'])])
  93. d['num_txouts'] = readVInt()
  94. d['txouts'] = MMGenList([{
  95. 'amount': bytes2coin_amt(bshift(8)),
  96. 'scriptPubKey': bshift(readVInt()).hex()
  97. } for i in range(d['num_txouts'])])
  98. for o in d['txouts']:
  99. o['address'] = scriptPubKey2addr(proto,o['scriptPubKey'])[0]
  100. if has_witness:
  101. # https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki
  102. # A non-witness program (defined hereinafter) txin MUST be associated with an empty
  103. # witness field, represented by a 0x00.
  104. d['txid'] = make_txid(tx[:4] + tx[6:var.idx] + tx[-4:])
  105. d['witness_size'] = len(tx) - var.idx + 2 - 4 # add len(marker+flag), subtract len(locktime)
  106. for txin in d['txins']:
  107. if tx[var.idx] == 0:
  108. bshift(1,skip=True)
  109. continue
  110. txin['witness'] = [
  111. bshift(readVInt(skip=True),skip=True).hex() for item in range(readVInt(skip=True)) ]
  112. else:
  113. d['txid'] = make_txid(tx)
  114. d['witness_size'] = 0
  115. if len(tx) - var.idx != 4:
  116. raise TxHexParseError('TX hex has invalid length: {} extra bytes'.format(len(tx)-var.idx-4))
  117. d['locktime'] = bytes2int(bshift(4))
  118. d['unsigned_hex'] = var.raw_tx.hex()
  119. return namedtuple('deserialized_tx',list(d.keys()))(**d)
  120. class Base(TxBase.Base):
  121. rel_fee_desc = 'satoshis per byte'
  122. rel_fee_disp = 'sat/byte'
  123. _deserialized = None
  124. class InputList(TxBase.Base.InputList):
  125. # Lexicographical Indexing of Transaction Inputs and Outputs
  126. # https://github.com/bitcoin/bips/blob/master/bip-0069.mediawiki
  127. def sort_bip69(self):
  128. def sort_func(a):
  129. return (
  130. bytes.fromhex(a.txid)
  131. + int.to_bytes(a.vout,4,'big') )
  132. self.sort(key=sort_func)
  133. class OutputList(TxBase.Base.OutputList):
  134. def sort_bip69(self):
  135. def sort_func(a):
  136. return (
  137. int.to_bytes(a.amt.to_unit('satoshi'),8,'big')
  138. + bytes.fromhex(addr2scriptPubKey(self.parent.proto,a.addr)) )
  139. self.sort(key=sort_func)
  140. def has_segwit_inputs(self):
  141. return any(i.mmtype in ('S','B') for i in self.inputs)
  142. def has_segwit_outputs(self):
  143. return any(o.mmtype in ('S','B') for o in self.outputs)
  144. # https://bitcoin.stackexchange.com/questions/1195/how-to-calculate-transaction-size-before-sending
  145. # 180: uncompressed, 148: compressed
  146. def estimate_size_old(self):
  147. if not self.inputs or not self.outputs:
  148. return None
  149. return len(self.inputs)*180 + len(self.outputs)*34 + 10
  150. # https://bitcoincore.org/en/segwit_wallet_dev/
  151. # vsize: 3 times of the size with original serialization, plus the size with new
  152. # serialization, divide the result by 4 and round up to the next integer.
  153. # TODO: results differ slightly from actual transaction size
  154. def estimate_size(self):
  155. if not self.inputs or not self.outputs:
  156. return None
  157. sig_size = 72 # sig in DER format
  158. pubkey_size_uncompressed = 65
  159. pubkey_size_compressed = 33
  160. def get_inputs_size():
  161. # txid vout [scriptSig size (vInt)] scriptSig (<sig> <pubkey>) nSeq
  162. isize_common = 32 + 4 + 1 + 4 # txid vout [scriptSig size] nSeq = 41
  163. input_size = {
  164. 'L': isize_common + sig_size + pubkey_size_uncompressed, # = 180
  165. 'C': isize_common + sig_size + pubkey_size_compressed, # = 148
  166. 'S': isize_common + 23, # = 64
  167. 'B': isize_common + 0 # = 41
  168. }
  169. ret = sum(input_size[i.mmtype] for i in self.inputs if i.mmtype)
  170. # We have no way of knowing whether a non-MMGen P2PKH addr is compressed or uncompressed
  171. # until we see the key, so assume compressed for fee-estimation purposes. If fee estimate
  172. # is off by more than 5%, sign() aborts and user is instructed to use --vsize-adj option.
  173. return ret + sum(input_size['C'] for i in self.inputs if not i.mmtype)
  174. def get_outputs_size():
  175. # output bytes = amt: 8, byte_count: 1+, pk_script
  176. # pk_script bytes: p2pkh: 25, p2sh: 23, bech32: 22
  177. return sum({'p2pkh':34,'p2sh':32,'bech32':31}[o.addr.addr_fmt] for o in self.outputs)
  178. # https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki
  179. # The witness is a serialization of all witness data of the transaction. Each txin is
  180. # associated with a witness field. A witness field starts with a var_int to indicate the
  181. # number of stack items for the txin. It is followed by stack items, with each item starts
  182. # with a var_int to indicate the length. Witness data is NOT script.
  183. # A non-witness program txin MUST be associated with an empty witness field, represented
  184. # by a 0x00. If all txins are not witness program, a transaction's wtxid is equal to its txid.
  185. def get_witness_size():
  186. if not self.has_segwit_inputs():
  187. return 0
  188. wf_size = 1 + 1 + sig_size + 1 + pubkey_size_compressed # vInt vInt sig vInt pubkey = 108
  189. return sum((1,wf_size)[i.mmtype in ('S','B')] for i in self.inputs)
  190. isize = get_inputs_size()
  191. osize = get_outputs_size()
  192. wsize = get_witness_size()
  193. # TODO: compute real varInt sizes instead of assuming 1 byte
  194. # old serialization: [nVersion] [vInt][txins][vInt][txouts] [nLockTime]
  195. old_size = 4 + 1 + isize + 1 + osize + 4
  196. # marker = 0x00, flag = 0x01
  197. # new serialization: [nVersion][marker][flag][vInt][txins][vInt][txouts][witness][nLockTime]
  198. new_size = 4 + 1 + 1 + 1 + isize + 1 + osize + wsize + 4 \
  199. if wsize else old_size
  200. ret = (old_size * 3 + new_size) // 4
  201. dmsg('\nData from estimate_size():')
  202. dmsg(f' inputs size: {isize}, outputs size: {osize}, witness size: {wsize}')
  203. dmsg(f' size: {new_size}, vsize: {ret}, old_size: {old_size}')
  204. return int(ret * (opt.vsize_adj or 1))
  205. # convert absolute CoinAmt fee to sat/byte using estimated size
  206. def fee_abs2rel(self,abs_fee,to_unit='satoshi'):
  207. return int(
  208. abs_fee /
  209. getattr( self.proto.coin_amt, to_unit ) /
  210. self.estimate_size() )
  211. @property
  212. def deserialized(self):
  213. if not self._deserialized:
  214. self._deserialized = DeserializeTX(self.proto,self.serialized)
  215. return self._deserialized
  216. def update_serialized(self,data):
  217. self.serialized = HexStr(data)
  218. self._deserialized = None
  219. self.check_serialized_integrity()
  220. def check_serialized_integrity(self):
  221. """
  222. Check that a malicious, compromised or malfunctioning coin daemon hasn't produced bad
  223. serialized tx data.
  224. Does not check witness data.
  225. Perform this check every time a serialized tx is received from the coin daemon or read
  226. from a transaction file.
  227. """
  228. def do_error(errmsg):
  229. from ....exception import TxHexMismatch
  230. raise TxHexMismatch(errmsg+'\n'+hdr)
  231. def check_equal(desc,hexio,mmio):
  232. if mmio != hexio:
  233. msg('\nMMGen {d}:\n{m}\nSerialized {d}:\n{h}'.format(
  234. d = desc,
  235. m = pp_fmt(mmio),
  236. h = pp_fmt(hexio) ))
  237. do_error(
  238. f'{desc.capitalize()} in serialized transaction data from coin daemon ' +
  239. 'do not match those in MMGen transaction!' )
  240. hdr = 'A malicious or malfunctioning coin daemon or other program may have altered your data!'
  241. dtx = self.deserialized
  242. if dtx.locktime != int(self.locktime or 0):
  243. do_error(
  244. f'Transaction hex nLockTime ({dtx.locktime}) ' +
  245. f'does not match MMGen transaction nLockTime ({self.locktime})' )
  246. check_equal(
  247. 'sequence numbers',
  248. [int(i['nSeq'],16) for i in dtx.txins],
  249. [i.sequence or self.proto.max_int for i in self.inputs] )
  250. check_equal(
  251. 'inputs',
  252. sorted((i['txid'],i['vout']) for i in dtx.txins),
  253. sorted((i.txid,i.vout) for i in self.inputs) )
  254. check_equal(
  255. 'outputs',
  256. sorted((o['address'],self.proto.coin_amt(o['amount'])) for o in dtx.txouts),
  257. sorted((o.addr,o.amt) for o in self.outputs) )
  258. if str(self.txid) != make_chksum_6(bytes.fromhex(dtx.unsigned_hex)).upper():
  259. do_error(
  260. f'MMGen TxID ({self.txid}) does not match serialized transaction data!')