transaction.py 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  1. #!/usr/bin/env python3
  2. #
  3. # MMGen Wallet, a terminal-based cryptocurrency wallet
  4. # Copyright (C)2013-2026 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-wallet
  9. # https://gitlab.com/mmgen/mmgen-wallet
  10. """
  11. proto.eth.tx.transaction: Ethereum serialized transaction class
  12. """
  13. # Adapted from:
  14. # https://github.com/ethereum/pyethereum/blob/master/ethereum/transactions.py
  15. # Copyright (c) 2015 Vitalik Buterin, Heiko Hees (MIT License)
  16. #
  17. # Modified to use libsecp256k1 instead of py_ecc for all EC operations
  18. from enum import Enum
  19. from ....util import cached_property
  20. from ....util2 import get_keccak
  21. from ....protocol import CoinProtocol
  22. from ...secp256k1.secp256k1 import sign_msghash, pubkey_recover, pubkey_gen
  23. from .. import rlp
  24. from ..rlp.sedes import big_endian_int, binary, Binary
  25. secp256k1_ge = CoinProtocol.Secp256k1.secp256k1_group_order
  26. null_address = b'\xff' * 20
  27. keccak_256 = get_keccak()
  28. def sha3(bytes_data):
  29. return keccak_256(bytes_data).digest()
  30. def mk_contract_addr(sender, nonce):
  31. return sha3(rlp.encode([sender, nonce]))[12:]
  32. class EthereumTransactionError(Exception):
  33. pass
  34. class gas_code(Enum):
  35. GTXCOST = 21000 # TX BASE GAS COST
  36. GTXDATAZERO = 4 # TX DATA ZERO BYTE GAS COST
  37. GTXDATANONZERO = 68 # TX DATA NON ZERO BYTE GAS COST
  38. class Transaction(rlp.Serializable):
  39. """
  40. A transaction is stored as:
  41. [nonce, gasprice, startgas, to, value, data, v, r, s]
  42. nonce is the number of transactions already sent by that account, encoded
  43. in binary form (eg. 0 -> '', 7 -> '\x07', 1000 -> '\x03\xd8').
  44. (v,r,s) is the raw Electrum-style signature of the transaction without the
  45. signature made with the private key corresponding to the sending account,
  46. with 0 <= v <= 3. From an Electrum-style signature (65 bytes) it is
  47. possible to extract the public key, and thereby the address, directly.
  48. A valid transaction is one where:
  49. (i) the signature is well-formed (ie. 0 <= v <= 3, 0 <= r < P, 0 <= s < N,
  50. 0 <= r < P - N if v >= 2), and
  51. (ii) the sending account has enough funds to pay the fee and the value.
  52. """
  53. fields = [
  54. ('nonce', big_endian_int),
  55. ('gasprice', big_endian_int),
  56. ('startgas', big_endian_int),
  57. ('to', Binary.fixed_length(20, allow_empty=True)),
  58. ('value', big_endian_int),
  59. ('data', binary),
  60. ('v', big_endian_int),
  61. ('r', big_endian_int),
  62. ('s', big_endian_int)]
  63. def __init__(self, nonce, gasprice, startgas, to, value, data, v=0, r=0, s=0):
  64. super().__init__(
  65. nonce,
  66. gasprice,
  67. startgas,
  68. to,
  69. value,
  70. data,
  71. v,
  72. r,
  73. s)
  74. # https://ethereum.stackexchange.com/questions/35481/
  75. # deriving-the-v-value-from-an-ecdsa-signature-without-web3j
  76. #
  77. # https://ethereum.stackexchange.com/questions/35764/r-s-v-ecdsa-packet-signatures/35770
  78. #
  79. # Basically this code from the bitcoin core libraries define the v value
  80. #
  81. # *recid = (overflow ? 2 : 0) | (secp256k1_fe_is_odd(&r.y) ? 1 : 0);
  82. #
  83. # That gets added to 27 for Ethereum, while 29 and 30 are invalid according to YellowPaper.io
  84. #
  85. # Please note that if an implementation supports EIP-155, instead of using 27 or 28,
  86. # it uses CHAIN_ID * 2 + 35. So the value of "v" can also be 37 or 38
  87. @property
  88. def recid(self):
  89. return self.v - 27 if self.v in (27, 28) else self.v - 35 - self.network_id * 2
  90. @property
  91. def network_id(self):
  92. if self.r == 0 and self.s == 0:
  93. return self.v
  94. elif self.v in (27, 28):
  95. return None
  96. else:
  97. return ((self.v - 1) // 2) - 17
  98. def get_sighash(self, network_id):
  99. if network_id is None:
  100. return sha3(rlp.encode(unsigned_tx_from_tx(self), UnsignedTransaction))
  101. else:
  102. assert 1 <= network_id < 2 ** 63 - 18, f'{network_id}: invalid network ID'
  103. return sha3(rlp.encode(
  104. rlp.infer_sedes(self).serialize(self)[:-3]
  105. + [network_id, b'', b'']))
  106. def sign(self, key, network_id=None):
  107. """
  108. Sign transaction with a private key, overwriting any existing signature
  109. """
  110. sig, recid = sign_msghash(self.get_sighash(network_id), bytes.fromhex(key))
  111. ret = self.copy(
  112. v = 27 + recid if network_id is None else 35 + recid + network_id * 2,
  113. r = int.from_bytes(sig[:32], 'big'),
  114. s = int.from_bytes(sig[32:], 'big'))
  115. ret._sender = sha3(pubkey_gen(bytes.fromhex(key), 0)[1:])[12:]
  116. return ret
  117. @cached_property
  118. def sender(self):
  119. """
  120. Recover transaction sender from signature
  121. """
  122. if self.r == 0 and self.s == 0:
  123. return null_address
  124. # In the yellow paper it is specified that s should be smaller than secp256k1_ge (eq.205)
  125. elif self.r >= secp256k1_ge or self.s >= secp256k1_ge or self.r == 0 or self.s == 0:
  126. raise EthereumTransactionError('Invalid signature (r or s) values!')
  127. return sha3(pubkey_recover(
  128. self.get_sighash(self.network_id),
  129. self.r.to_bytes(32, 'big') + self.s.to_bytes(32, 'big'),
  130. self.recid,
  131. False)[1:])[12:]
  132. @property
  133. def hash(self):
  134. return sha3(rlp.encode(self))
  135. def to_dict(self):
  136. d = {}
  137. for name, _ in self.__class__._meta.fields:
  138. d[name] = getattr(self, name)
  139. if name in ('to', 'data'):
  140. d[name] = '0x' + d[name].hex()
  141. d['sender'] = '0x' + self.sender.hex()
  142. d['hash'] = '0x' + self.hash.hex()
  143. return d
  144. @property
  145. def intrinsic_gas_used(self):
  146. num_zero_bytes = self.data.count(0)
  147. num_non_zero_bytes = len(self.data) - num_zero_bytes
  148. return (
  149. gas_code.GTXCOST
  150. # + (0 if self.to else gas_code.CREATE[3])
  151. + gas_code.GTXDATAZERO * num_zero_bytes
  152. + gas_code.GTXDATANONZERO * num_non_zero_bytes)
  153. @property
  154. def creates(self):
  155. 'returns the address of the contract created by this transaction'
  156. if self.to in (b'', bytes(20)):
  157. return mk_contract_addr(self.sender, self.nonce)
  158. def __eq__(self, other):
  159. return isinstance(other, self.__class__) and self.hash == other.hash
  160. def __lt__(self, other):
  161. return isinstance(other, self.__class__) and self.hash < other.hash
  162. def __hash__(self):
  163. return int.from_bytes(self.hash, 'big')
  164. def __ne__(self, other):
  165. return not self.__eq__(other)
  166. def __repr__(self):
  167. return '<Transaction_(%s)>' % self.hash.hex()[:8]
  168. def __structlog__(self):
  169. return self.hash.hex()
  170. class UnsignedTransaction(rlp.Serializable):
  171. fields = [(field, sedes) for field, sedes in Transaction._meta.fields if field not in 'vrs']
  172. def unsigned_tx_from_tx(tx):
  173. return UnsignedTransaction(
  174. nonce = tx.nonce,
  175. gasprice = tx.gasprice,
  176. startgas = tx.startgas,
  177. to = tx.to,
  178. value = tx.value,
  179. data = tx.data)