diff --git a/cic_tools/eth/checksum.py b/cic_tools/eth/address.py similarity index 99% rename from cic_tools/eth/checksum.py rename to cic_tools/eth/address.py index 3b235d4..d49ae32 100644 --- a/cic_tools/eth/checksum.py +++ b/cic_tools/eth/address.py @@ -7,7 +7,6 @@ from hexathon import ( def to_checksum(address_hex): - address_hex = strip_0x(address_hex) address_hex = uniform(address_hex) h = sha3.keccak_256() diff --git a/cic_tools/eth/runnable/balance.py b/cic_tools/eth/runnable/balance.py index 208c0c2..9687132 100644 --- a/cic_tools/eth/runnable/balance.py +++ b/cic_tools/eth/runnable/balance.py @@ -25,7 +25,7 @@ import sha3 from eth_abi import encode_single # local imports -from cic_tools.eth.checksum import to_checksum +from cic_tools.eth.address import to_checksum from cic_tools.eth.method import ( jsonrpc_template, erc20_balance, diff --git a/cic_tools/eth/runnable/checksum.py b/cic_tools/eth/runnable/checksum.py index 9a8d2af..a4d1d59 100644 --- a/cic_tools/eth/runnable/checksum.py +++ b/cic_tools/eth/runnable/checksum.py @@ -2,7 +2,7 @@ import sys # local imports -from cic_tools.eth.checksum import to_checksum +from cic_tools.eth.address import to_checksum print(to_checksum(sys.argv[1])) diff --git a/cic_tools/eth/runnable/decode.py b/cic_tools/eth/runnable/decode.py new file mode 100644 index 0000000..32bd3b9 --- /dev/null +++ b/cic_tools/eth/runnable/decode.py @@ -0,0 +1,51 @@ +#!python3 + +"""Decode raw transaction + +.. moduleauthor:: Louis Holbrook +.. pgp:: 0826EDA1702D1E87C6E2875121D2E7BB88C2A746 + +""" + +# SPDX-License-Identifier: GPL-3.0-or-later + +# standard imports +import os +import json +import argparse +import logging + +# third-party imports +from cic_tools.eth.tx import unpack_signed + + +logging.basicConfig(level=logging.WARNING) +logg = logging.getLogger() + +default_abi_dir = os.environ.get('ETH_ABI_DIR', '/usr/share/local/cic/solidity/abi') +default_eth_provider = os.environ.get('ETH_PROVIDER', 'http://localhost:8545') + +argparser = argparse.ArgumentParser() +argparser.add_argument('-v', action='store_true', help='Be verbose') +argparser.add_argument('-i', '--chain-id', dest='i', type=str, help='Numeric network id') +argparser.add_argument('tx', type=str, help='hex-encoded signed raw transaction') +args = argparser.parse_args() + +if args.v: + logg.setLevel(logging.DEBUG) + +(chain_name, chain_id) = args.i.split(':') + + +def main(): + tx_raw = args.tx + if tx_raw[:2] == '0x': + tx_raw = tx_raw[2:] + tx_raw_bytes = bytes.fromhex(tx_raw) + tx = unpack_signed(tx_raw_bytes, int(chain_id)) + for k in tx.keys(): + print('{}: {}'.format(k, tx[k])) + + +if __name__ == '__main__': + main() diff --git a/cic_tools/eth/tx.py b/cic_tools/eth/tx.py new file mode 100644 index 0000000..b92861f --- /dev/null +++ b/cic_tools/eth/tx.py @@ -0,0 +1,83 @@ +# standard imports +import logging + +# third-party imports +import sha3 +from eth_keys import KeyAPI +from eth_keys.backends import NativeECCBackend +from rlp import decode as rlp_decode +from rlp import encode as rlp_encode + +# local imports +from .address import to_checksum + +logg = logging.getLogger(__name__) + + +field_debugs = [ + 'nonce', + 'gasPrice', + 'gas', + 'to', + 'value', + 'data', + 'v', + 'r', + 's', + ] + +def unpack_signed(tx_raw_bytes, chain_id=1): + d = rlp_decode(tx_raw_bytes) + + logg.debug('decoding using chain id {}'.format(chain_id)) + j = 0 + for i in d: + logg.debug('decoded {}: {}'.format(field_debugs[j], i.hex())) + j += 1 + vb = chain_id + if chain_id != 0: + v = int.from_bytes(d[6], 'big') + vb = v - (chain_id * 2) - 35 + s = b''.join([d[7], d[8], bytes([vb])]) + so = KeyAPI.Signature(signature_bytes=s) + + h = sha3.keccak_256() + h.update(rlp_encode(d)) + signed_hash = h.digest() + + d[6] = chain_id + d[7] = b'' + d[8] = b'' + + h = sha3.keccak_256() + h.update(rlp_encode(d)) + unsigned_hash = h.digest() + + p = so.recover_public_key_from_msg_hash(unsigned_hash) + a = p.to_checksum_address() + logg.debug('decoded recovery byte {}'.format(vb)) + logg.debug('decoded address {}'.format(a)) + logg.debug('decoded signed hash {}'.format(signed_hash.hex())) + logg.debug('decoded unsigned hash {}'.format(unsigned_hash.hex())) + + to = d[3].hex() or None + if to != None: + to = to_checksum(to) + + return { + 'from': a, + 'nonce': int.from_bytes(d[0], 'big'), + 'gasPrice': int.from_bytes(d[1], 'big'), + 'gas': int.from_bytes(d[2], 'big'), + 'to': to, + 'value': int.from_bytes(d[4], 'big'), + 'data': '0x' + d[5].hex(), + 'v': chain_id, + 'r': '0x' + s[:32].hex(), + 's': '0x' + s[32:64].hex(), + 'chainId': chain_id, + 'hash': '0x' + signed_hash.hex(), + 'hash_unsigned': '0x' + unsigned_hash.hex(), + } + + diff --git a/requirements.txt b/requirements.txt index a9e6ccf..e854a1b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,3 +11,4 @@ crypto-dev-signer==0.4.13rc2 pysha3==1.0.2 hexathon==0.0.1a2 eth-abi==2.1.1 +eth-keys==0.3.3