From 856058941cc207a69386116d164ec6b8206d4216 Mon Sep 17 00:00:00 2001 From: The MMGen Project Date: Sat, 26 Apr 2025 10:38:55 +0000 Subject: [PATCH] proto.eth.tx.base: check serialized data integrity --- mmgen/data/version | 2 +- mmgen/proto/eth/tx/base.py | 44 ++++++++++++++++++++++++++++++++-- mmgen/proto/eth/tx/unsigned.py | 4 +++- mmgen/tx/base.py | 1 + mmgen/tx/completed.py | 13 +++++++--- 5 files changed, 57 insertions(+), 7 deletions(-) diff --git a/mmgen/data/version b/mmgen/data/version index 258541a5..095135a0 100644 --- a/mmgen/data/version +++ b/mmgen/data/version @@ -1 +1 @@ -15.1.dev29 +15.1.dev30 diff --git a/mmgen/proto/eth/tx/base.py b/mmgen/proto/eth/tx/base.py index ff4a8ba4..508cee7a 100755 --- a/mmgen/proto/eth/tx/base.py +++ b/mmgen/proto/eth/tx/base.py @@ -89,9 +89,49 @@ class Base(TxBase): tx = tx, rx = rx) - def check_serialized_integrity(self): # TODO - return True + def check_serialized_integrity(self): + if self.signed: + from .. import rlp + o = self.txobj + d = rlp.decode(bytes.fromhex(self.serialized)) + to_key = 'token_addr' if self.is_token else 'to' + + if o['nonce'] == 0: + assert d[0] == b'', f'{d[0]}: invalid nonce in serialized data' + else: + assert int(d[0].hex(), 16) == o['nonce'], f'{d[0]}: invalid nonce in serialized data' + if o.get(to_key): + assert d[3].hex() == o[to_key], f'{d[3].hex()}: invalid ‘to’ address in serialized data' + if not self.is_token: + if o['amt']: + assert int(d[4].hex(), 16) == o['amt'].to_unit('wei'), ( + f'{d[4].hex()}: invalid amt in serialized data') + if self.is_swap: + assert d[5] == self.swap_memo.encode(), ( + f'{d[5]}: invalid swap memo in serialized data') class TokenBase(Base): dfl_gas = 52000 contract_desc = 'token contract' + + def check_serialized_integrity(self): + if self.signed: + super().check_serialized_integrity() + + from .. import rlp + from ....amt import TokenAmt + d = rlp.decode(bytes.fromhex(self.serialized)) + o = self.txobj + + assert d[4] == b'', f'{d[4]}: non-empty amount field in token transaction in serialized data' + + data = d[5].hex() + assert data[:8] == 'a9059cbb', ( + f'{data[:8]}: invalid MethodID for op ‘transfer’ in serialized data') + assert data[32:72] == o['token_to'], ( + f'{data[32:72]}: invalid ‘token_to‘ address in serialized data') + assert TokenAmt( + int(data[72:], 16), + decimals = o['decimals'], + from_unit = 'atomic') == o['amt'], ( + f'{data[72:]}: invalid amt in serialized data') diff --git a/mmgen/proto/eth/tx/unsigned.py b/mmgen/proto/eth/tx/unsigned.py index 410fc66a..191687fc 100755 --- a/mmgen/proto/eth/tx/unsigned.py +++ b/mmgen/proto/eth/tx/unsigned.py @@ -94,7 +94,9 @@ class Unsigned(Completed, TxBase.Unsigned): await self.do_sign(o, keys[0].sec.wif) msg('OK') from ....tx import SignedTX - return await SignedTX(cfg=self.cfg, data=self.__dict__, automount=self.automount) + tx = await SignedTX(cfg=self.cfg, data=self.__dict__, automount=self.automount) + tx.check_serialized_integrity() + return tx except Exception as e: msg(f'{e}: transaction signing failed!') return False diff --git a/mmgen/tx/base.py b/mmgen/tx/base.py index 76421864..982970bf 100755 --- a/mmgen/tx/base.py +++ b/mmgen/tx/base.py @@ -123,6 +123,7 @@ class Base(MMGenObject): self.name = type(self).__name__ self.proto = kwargs['proto'] self.twctl = kwargs.get('twctl') + self.is_token = 'Token' in self.name @property def coin(self): diff --git a/mmgen/tx/completed.py b/mmgen/tx/completed.py index 7d69839e..54cf3f21 100755 --- a/mmgen/tx/completed.py +++ b/mmgen/tx/completed.py @@ -31,9 +31,16 @@ class Completed(Base): self.name = type(self).__name__ else: from .file import MMGenTxFile - MMGenTxFile(self).parse(str(filename), quiet_open=quiet_open) - - self.check_serialized_integrity() + try: + MMGenTxFile(self).parse(str(filename), quiet_open=quiet_open) + self.check_serialized_integrity() + except Exception as e: + from ..color import orange + from ..util import msg + msg(orange( + f'Something is wrong with transaction file ‘{filename}’\n' + 'To fix this problem, please move or delete the file')) + raise e # repeat with sign and send, because coin daemon could be restarted self.check_correct_chain()