-
Notifications
You must be signed in to change notification settings - Fork 18
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Remove dependencies on pysha3 and ethereum modules
- pysha3: Monero and Ethereum use the old pre-SHA3 version of the keccak hash function, which is not supported by hashlib.sha3. The pysha3 package supports the function but is not portable. Therefore, use the pure-Python implementation mmgen.keccak as a fallback, making the pysha3 package optional. Use of mmgen.keccak may be forced with --use-internal-keccak-module mmgen.keccak was ported from the Python 2 implementation at https://github.com/ctz/keccak - ethereum (pyethereum): Installation of this package presents numerous problems due to poor maintenance and many superfluous dependencies. Therefore, use stripped-down and modified local versions of ethereum.transactions and ethereum.utils as fallbacks, making the ethereum package optional. The local pyethereum.utils uses mmgen.keccak as a fallback for pysha3's keccak implementation.
- Loading branch information
Showing
17 changed files
with
1,083 additions
and
74 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,215 @@ | ||
# | ||
# Adapted from: # https://github.com/ethereum/pyethereum/blob/master/ethereum/transactions.py | ||
# | ||
import rlp | ||
from rlp.sedes import big_endian_int,binary | ||
from mmgen.altcoins.eth.pyethereum.utils import ( | ||
str_to_bytes,encode_hex,ascii_chr,big_endian_to_int,TT256,mk_contract_address, | ||
ecsign,ecrecover_to_pub,normalize_key ) | ||
import mmgen.altcoins.eth.pyethereum.utils as utils | ||
|
||
class InvalidTransaction(Exception): pass | ||
class opcodes(object): | ||
GTXCOST = 21000 # TX BASE GAS COST | ||
GTXDATAZERO = 4 # TX DATA ZERO BYTE GAS COST | ||
GTXDATANONZERO = 68 # TX DATA NON ZERO BYTE GAS COST | ||
|
||
# in the yellow paper it is specified that s should be smaller than | ||
# secpk1n (eq.205) | ||
secpk1n = 115792089237316195423570985008687907852837564279074904382605163141518161494337 | ||
null_address = b'\xff' * 20 | ||
|
||
class Transaction(rlp.Serializable): | ||
|
||
""" | ||
A transaction is stored as: | ||
[nonce, gasprice, startgas, to, value, data, v, r, s] | ||
nonce is the number of transactions already sent by that account, encoded | ||
in binary form (eg. 0 -> '', 7 -> '\x07', 1000 -> '\x03\xd8'). | ||
(v,r,s) is the raw Electrum-style signature of the transaction without the | ||
signature made with the private key corresponding to the sending account, | ||
with 0 <= v <= 3. From an Electrum-style signature (65 bytes) it is | ||
possible to extract the public key, and thereby the address, directly. | ||
A valid transaction is one where: | ||
(i) the signature is well-formed (ie. 0 <= v <= 3, 0 <= r < P, 0 <= s < N, | ||
0 <= r < P - N if v >= 2), and | ||
(ii) the sending account has enough funds to pay the fee and the value. | ||
""" | ||
|
||
fields = [ | ||
('nonce', big_endian_int), | ||
('gasprice', big_endian_int), | ||
('startgas', big_endian_int), | ||
('to', utils.address), | ||
('value', big_endian_int), | ||
('data', binary), | ||
('v', big_endian_int), | ||
('r', big_endian_int), | ||
('s', big_endian_int), | ||
] | ||
|
||
_sender = None | ||
|
||
def __init__(self, nonce, gasprice, startgas, to, value, data, v=0, r=0, s=0): | ||
# self.data = None | ||
|
||
to = utils.normalize_address(to, allow_blank=True) | ||
|
||
super( | ||
Transaction, | ||
self).__init__( | ||
nonce, | ||
gasprice, | ||
startgas, | ||
to, | ||
value, | ||
data, | ||
v, | ||
r, | ||
s) | ||
|
||
if self.gasprice >= TT256 or self.startgas >= TT256 or \ | ||
self.value >= TT256 or self.nonce >= TT256: | ||
raise InvalidTransaction("Values way too high!") | ||
|
||
@property | ||
def sender(self): | ||
if not self._sender: | ||
# Determine sender | ||
if self.r == 0 and self.s == 0: | ||
self._sender = null_address | ||
else: | ||
if self.v in (27, 28): | ||
vee = self.v | ||
sighash = utils.sha3(rlp.encode(unsigned_tx_from_tx(self), UnsignedTransaction)) | ||
|
||
elif self.v >= 37: | ||
vee = self.v - self.network_id * 2 - 8 | ||
assert vee in (27, 28) | ||
rlpdata = rlp.encode(rlp.infer_sedes(self).serialize(self)[:-3] + [self.network_id, '', '']) | ||
sighash = utils.sha3(rlpdata) | ||
else: | ||
raise InvalidTransaction("Invalid V value") | ||
if self.r >= secpk1n or self.s >= secpk1n or self.r == 0 or self.s == 0: | ||
raise InvalidTransaction("Invalid signature values!") | ||
pub = ecrecover_to_pub(sighash, vee, self.r, self.s) | ||
if pub == b'\x00' * 64: | ||
raise InvalidTransaction( | ||
"Invalid signature (zero privkey cannot sign)") | ||
self._sender = utils.sha3(pub)[-20:] | ||
return self._sender | ||
|
||
@property | ||
def network_id(self): | ||
if self.r == 0 and self.s == 0: | ||
return self.v | ||
elif self.v in (27, 28): | ||
return None | ||
else: | ||
return ((self.v - 1) // 2) - 17 | ||
|
||
@sender.setter | ||
def sender(self, value): | ||
self._sender = value | ||
|
||
def sign(self, key, network_id=None): | ||
"""Sign this transaction with a private key. | ||
A potentially already existing signature would be overridden. | ||
""" | ||
if network_id is None: | ||
rawhash = utils.sha3(rlp.encode(unsigned_tx_from_tx(self), UnsignedTransaction)) | ||
else: | ||
assert 1 <= network_id < 2**63 - 18 | ||
rlpdata = rlp.encode(rlp.infer_sedes(self).serialize(self)[:-3] + [network_id, b'', b'']) | ||
rawhash = utils.sha3(rlpdata) | ||
|
||
key = normalize_key(key) | ||
|
||
v, r, s = ecsign(rawhash, key) | ||
if network_id is not None: | ||
v += 8 + network_id * 2 | ||
|
||
ret = self.copy( | ||
v=v, r=r, s=s | ||
) | ||
ret._sender = utils.privtoaddr(key) | ||
return ret | ||
|
||
@property | ||
def hash(self): | ||
return utils.sha3(rlp.encode(self)) | ||
|
||
def to_dict(self): | ||
d = {} | ||
for name, _ in self.__class__._meta.fields: | ||
d[name] = getattr(self, name) | ||
if name in ('to', 'data'): | ||
d[name] = '0x' + encode_hex(d[name]) | ||
d['sender'] = '0x' + encode_hex(self.sender) | ||
d['hash'] = '0x' + encode_hex(self.hash) | ||
return d | ||
|
||
@property | ||
def intrinsic_gas_used(self): | ||
num_zero_bytes = str_to_bytes(self.data).count(ascii_chr(0)) | ||
num_non_zero_bytes = len(self.data) - num_zero_bytes | ||
return (opcodes.GTXCOST | ||
# + (0 if self.to else opcodes.CREATE[3]) | ||
+ opcodes.GTXDATAZERO * num_zero_bytes | ||
+ opcodes.GTXDATANONZERO * num_non_zero_bytes) | ||
|
||
@property | ||
def creates(self): | ||
"returns the address of a contract created by this tx" | ||
if self.to in (b'', '\0' * 20): | ||
return mk_contract_address(self.sender, self.nonce) | ||
|
||
def __eq__(self, other): | ||
return isinstance(other, self.__class__) and self.hash == other.hash | ||
|
||
def __lt__(self, other): | ||
return isinstance(other, self.__class__) and self.hash < other.hash | ||
|
||
def __hash__(self): | ||
return utils.big_endian_to_int(self.hash) | ||
|
||
def __ne__(self, other): | ||
return not self.__eq__(other) | ||
|
||
def __repr__(self): | ||
return '<Transaction(%s)>' % encode_hex(self.hash)[:4] | ||
|
||
def __structlog__(self): | ||
return encode_hex(self.hash) | ||
|
||
# This method should be called for block numbers >= HOMESTEAD_FORK_BLKNUM only. | ||
# The >= operator is replaced by > because the integer division N/2 always produces the value | ||
# which is by 0.5 less than the real N/2 | ||
def check_low_s_metropolis(self): | ||
if self.s > secpk1n // 2: | ||
raise InvalidTransaction("Invalid signature S value!") | ||
|
||
def check_low_s_homestead(self): | ||
if self.s > secpk1n // 2 or self.s == 0: | ||
raise InvalidTransaction("Invalid signature S value!") | ||
|
||
|
||
class UnsignedTransaction(rlp.Serializable): | ||
fields = [ | ||
(field, sedes) for field, sedes in Transaction._meta.fields | ||
if field not in "vrs" | ||
] | ||
|
||
def unsigned_tx_from_tx(tx): | ||
return UnsignedTransaction( | ||
nonce=tx.nonce, | ||
gasprice=tx.gasprice, | ||
startgas=tx.startgas, | ||
to=tx.to, | ||
value=tx.value, | ||
data=tx.data, | ||
) |
Oops, something went wrong.