From 2fc47a6b4366f38af7c11bea8d4252e63b1966b7 Mon Sep 17 00:00:00 2001 From: Karl Bartel Date: Mon, 24 Jun 2019 16:51:55 +0200 Subject: [PATCH 01/19] Simpler signing for `UpdatePFS` --- raiden/encoding/messages.py | 17 ---------------- raiden/messages.py | 40 +++++++++++++++++++------------------ 2 files changed, 21 insertions(+), 36 deletions(-) diff --git a/raiden/encoding/messages.py b/raiden/encoding/messages.py index 97388ef6e3..5d792ba67e 100644 --- a/raiden/encoding/messages.py +++ b/raiden/encoding/messages.py @@ -209,23 +209,6 @@ def cmdid(id_): ) -PFSCapacityUpdate = namedbuffer( - "update_pfs", - [ - chain_id, - token_network_address, - channel_identifier, - updating_participant, - other_participant, - updating_nonce, - other_nonce, - updating_capacity, - other_capacity, - reveal_timeout, - signature, - ], -) - ToDevice = namedbuffer("to_device", [cmdid(TODEVICE), pad(3), message_identifier, signature]) WithdrawRequest = namedbuffer( diff --git a/raiden/messages.py b/raiden/messages.py index 998d95ea4b..d3cb7f19dd 100644 --- a/raiden/messages.py +++ b/raiden/messages.py @@ -1160,6 +1160,14 @@ def __post_init__(self): if self.signature is None: self.signature = EMPTY_SIGNATURE + # TODO: move to SignedMessage + def __eq__(self, other): + return ( + isinstance(other, self.__class__) + and self._data_to_sign() == other._data_to_sign() + and self.signature == other.signature + ) + @classmethod def from_channel_state(cls, channel_state: NettingChannelState) -> "PFSCapacityUpdate": # pylint: disable=unexpected-keyword-arg @@ -1179,25 +1187,19 @@ def from_channel_state(cls, channel_state: NettingChannelState) -> "PFSCapacityU signature=EMPTY_SIGNATURE, ) - def packed(self) -> bytes: - klass = messages.PFSCapacityUpdate - data = buffer_for(klass) - packed = klass(data) - self.pack(packed) - return packed - - def pack(self, packed) -> None: - packed.chain_id = self.canonical_identifier.chain_identifier - packed.token_network_address = self.canonical_identifier.token_network_address - packed.channel_identifier = self.canonical_identifier.channel_identifier - packed.updating_participant = self.updating_participant - packed.other_participant = self.other_participant - packed.updating_nonce = self.updating_nonce - packed.other_nonce = self.other_nonce - packed.updating_capacity = self.updating_capacity - packed.other_capacity = self.other_capacity - packed.reveal_timeout = self.reveal_timeout - packed.signature = self.signature + def _data_to_sign(self) -> bytes: + return pack_data( + (self.canonical_identifier.chain_identifier, "uint256"), + (self.canonical_identifier.token_network_address, "address"), + (self.canonical_identifier.channel_identifier, "uint256"), + (self.updating_participant, "address"), + (self.other_participant, "address"), + (self.updating_nonce, "uint64"), + (self.other_nonce, "uint64"), + (self.updating_capacity, "uint256"), + (self.other_capacity, "uint256"), + (self.reveal_timeout, "uint256"), + ) @dataclass From 04e70a777932e96318dafbc1240bcbca00a0c279 Mon Sep 17 00:00:00 2001 From: Karl Bartel Date: Mon, 24 Jun 2019 17:21:07 +0200 Subject: [PATCH 02/19] Simpler signing for `Processed` --- raiden/encoding/messages.py | 3 --- raiden/messages.py | 15 +++++++++++++++ 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/raiden/encoding/messages.py b/raiden/encoding/messages.py index 5d792ba67e..5fee16654d 100644 --- a/raiden/encoding/messages.py +++ b/raiden/encoding/messages.py @@ -70,8 +70,6 @@ def cmdid(id_): non_closing_signature = make_field("non_closing_signature", 65, "65s") reward_proof_signature = make_field("reward_proof_signature", 65, "65s") -Processed = namedbuffer("processed", [cmdid(PROCESSED), pad(3), message_identifier, signature]) - Delivered = namedbuffer( "delivered", [cmdid(DELIVERED), pad(3), delivered_message_identifier, signature] ) @@ -240,7 +238,6 @@ def cmdid(id_): CMDID_MESSAGE = { - PROCESSED: Processed, PING: Ping, PONG: Pong, SECRETREQUEST: SecretRequest, diff --git a/raiden/messages.py b/raiden/messages.py index d3cb7f19dd..1e1024d404 100644 --- a/raiden/messages.py +++ b/raiden/messages.py @@ -377,10 +377,25 @@ class Processed(SignedRetrieableMessage): message_identifier: MessageID + # TODO: move to SignedMessage + def __eq__(self, other): + return ( + isinstance(other, self.__class__) + and self._data_to_sign() == other._data_to_sign() + and self.signature == other.signature + ) + @classmethod def from_event(cls, event): return cls(message_identifier=event.message_identifier, signature=EMPTY_SIGNATURE) + def _data_to_sign(self) -> bytes: + return pack_data( + (self.cmdid, "uint8"), + (b"0" * 3, "bytes"), # padding + (self.message_identifier, "uint64"), + ) + @dataclass(repr=False, eq=False) class ToDevice(SignedMessage): From c183c802fe96ea0a12734417f21525365ea3c2ab Mon Sep 17 00:00:00 2001 From: Karl Bartel Date: Mon, 24 Jun 2019 17:30:17 +0200 Subject: [PATCH 03/19] Simplify signing for short messages `Ping`, `Pong`, `ToDevice`, `Delivered`. --- raiden/encoding/messages.py | 18 ----------- raiden/messages.py | 59 +++++++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 18 deletions(-) diff --git a/raiden/encoding/messages.py b/raiden/encoding/messages.py index 5fee16654d..b2dbba9c23 100644 --- a/raiden/encoding/messages.py +++ b/raiden/encoding/messages.py @@ -34,10 +34,6 @@ def cmdid(id_): payment_identifier = make_field("payment_identifier", 8, "8s", integer(0, UINT64_MAX)) chain_id = make_field("chain_id", 32, "32s", integer(0, UINT256_MAX)) message_identifier = make_field("message_identifier", 8, "8s", integer(0, UINT64_MAX)) -current_protocol_version = make_field("current_protocol_version", 1, "1s", integer(0, 256)) -delivered_message_identifier = make_field( - "delivered_message_identifier", 8, "8s", integer(0, UINT64_MAX) -) expiration = make_field("expiration", 32, "32s", integer(0, UINT256_MAX)) token_network_address = make_field("token_network_address", 20, "20s") @@ -70,14 +66,6 @@ def cmdid(id_): non_closing_signature = make_field("non_closing_signature", 65, "65s") reward_proof_signature = make_field("reward_proof_signature", 65, "65s") -Delivered = namedbuffer( - "delivered", [cmdid(DELIVERED), pad(3), delivered_message_identifier, signature] -) - -Ping = namedbuffer("ping", [cmdid(PING), pad(3), nonce, current_protocol_version, signature]) - -Pong = namedbuffer("pong", [cmdid(PONG), pad(3), nonce, signature]) - SecretRequest = namedbuffer( "secret_request", [ @@ -207,8 +195,6 @@ def cmdid(id_): ) -ToDevice = namedbuffer("to_device", [cmdid(TODEVICE), pad(3), message_identifier, signature]) - WithdrawRequest = namedbuffer( "withdraw_request", [ @@ -238,16 +224,12 @@ def cmdid(id_): CMDID_MESSAGE = { - PING: Ping, - PONG: Pong, SECRETREQUEST: SecretRequest, UNLOCK: Unlock, REVEALSECRET: RevealSecret, LOCKEDTRANSFER: LockedTransfer, REFUNDTRANSFER: RefundTransfer, - DELIVERED: Delivered, LOCKEXPIRED: LockExpired, - TODEVICE: ToDevice, WITHDRAW_REQUEST: WithdrawRequest, WITHDRAW: Withdraw, } diff --git a/raiden/messages.py b/raiden/messages.py index 1e1024d404..9b9f3f7095 100644 --- a/raiden/messages.py +++ b/raiden/messages.py @@ -409,6 +409,21 @@ class ToDevice(SignedMessage): message_identifier: MessageID + # TODO: move to SignedMessage + def __eq__(self, other): + return ( + isinstance(other, self.__class__) + and self._data_to_sign() == other._data_to_sign() + and self.signature == other.signature + ) + + def _data_to_sign(self) -> bytes: + return pack_data( + (self.cmdid, "uint8"), + (b"0" * 3, "bytes"), # padding + (self.message_identifier, "uint64"), + ) + @dataclass(repr=False, eq=False) class Delivered(SignedMessage): @@ -426,6 +441,21 @@ class Delivered(SignedMessage): delivered_message_identifier: MessageID + # TODO: move to SignedMessage + def __eq__(self, other): + return ( + isinstance(other, self.__class__) + and self._data_to_sign() == other._data_to_sign() + and self.signature == other.signature + ) + + def _data_to_sign(self) -> bytes: + return pack_data( + (self.cmdid, "uint8"), + (b"0" * 3, "bytes"), # padding + (self.delivered_message_identifier, "uint64"), + ) + @dataclass(repr=False, eq=False) class Pong(SignedMessage): @@ -435,6 +465,19 @@ class Pong(SignedMessage): nonce: Nonce + # TODO: move to SignedMessage + def __eq__(self, other): + return ( + isinstance(other, self.__class__) + and self._data_to_sign() == other._data_to_sign() + and self.signature == other.signature + ) + + def _data_to_sign(self) -> bytes: + return pack_data( + (self.cmdid, "uint8"), (b"0" * 3, "bytes"), (self.nonce, "uint64") # padding + ) + @dataclass(repr=False, eq=False) class Ping(SignedMessage): @@ -445,6 +488,22 @@ class Ping(SignedMessage): nonce: Nonce current_protocol_version: RaidenProtocolVersion + # TODO: move to SignedMessage + def __eq__(self, other): + return ( + isinstance(other, self.__class__) + and self._data_to_sign() == other._data_to_sign() + and self.signature == other.signature + ) + + def _data_to_sign(self) -> bytes: + return pack_data( + (self.cmdid, "uint8"), + (b"0" * 3, "bytes"), # padding + (self.nonce, "uint64"), + (self.current_protocol_version, "uint8"), + ) + @dataclass(repr=False, eq=False) class SecretRequest(SignedRetrieableMessage): From 08b28f86c1f235a1bd55d74f6b3bd6592ea75586 Mon Sep 17 00:00:00 2001 From: Karl Bartel Date: Mon, 24 Jun 2019 17:40:58 +0200 Subject: [PATCH 04/19] Simplify signing for `SecretRequest` --- raiden/encoding/messages.py | 14 -------------- raiden/messages.py | 11 +++++++++++ raiden/tests/unit/test_encoding.py | 16 +--------------- 3 files changed, 12 insertions(+), 29 deletions(-) diff --git a/raiden/encoding/messages.py b/raiden/encoding/messages.py index b2dbba9c23..be1e12cec3 100644 --- a/raiden/encoding/messages.py +++ b/raiden/encoding/messages.py @@ -66,19 +66,6 @@ def cmdid(id_): non_closing_signature = make_field("non_closing_signature", 65, "65s") reward_proof_signature = make_field("reward_proof_signature", 65, "65s") -SecretRequest = namedbuffer( - "secret_request", - [ - cmdid(SECRETREQUEST), - pad(3), - message_identifier, - payment_identifier, - secrethash, - amount, - expiration, - signature, - ], -) Unlock = namedbuffer( "unlock", @@ -224,7 +211,6 @@ def cmdid(id_): CMDID_MESSAGE = { - SECRETREQUEST: SecretRequest, UNLOCK: Unlock, REVEALSECRET: RevealSecret, LOCKEDTRANSFER: LockedTransfer, diff --git a/raiden/messages.py b/raiden/messages.py index 9b9f3f7095..747dbfeeaa 100644 --- a/raiden/messages.py +++ b/raiden/messages.py @@ -528,6 +528,17 @@ def from_event(cls, event): signature=EMPTY_SIGNATURE, ) + def _data_to_sign(self) -> bytes: + return pack_data( + (self.cmdid, "uint8"), + (b"0" * 3, "bytes"), # padding + (self.message_identifier, "uint64"), + (self.payment_identifier, "uint64"), + (self.secrethash, "bytes32"), + (self.amount, "uint256"), + (self.expiration, "uint256"), + ) + @dataclass(repr=False, eq=False) class Unlock(EnvelopeMessage): diff --git a/raiden/tests/unit/test_encoding.py b/raiden/tests/unit/test_encoding.py index b180d3b157..625cd48e2a 100644 --- a/raiden/tests/unit/test_encoding.py +++ b/raiden/tests/unit/test_encoding.py @@ -2,7 +2,7 @@ from raiden.encoding.encoders import integer from raiden.encoding.format import Field, make_field, namedbuffer -from raiden.encoding.messages import SECRETREQUEST, SecretRequest, wrap +from raiden.encoding.messages import SECRETREQUEST, wrap def test_integer_encoder(): @@ -43,17 +43,3 @@ def test_wrap_invalid(): assert wrap(data=[]) is None assert wrap(data=[10000]) is None, "Unknown cmdid" assert wrap(data=[SECRETREQUEST]) is None, "Length not equal to SecretRequest.size" - - -def test_wrap_and_namedbuffer(): - valid = [SECRETREQUEST] - valid.extend([0] * (SecretRequest.size - 1)) - message = wrap(data=valid) - assert type(message) == SecretRequest - assert message.amount == 0 - assert message.message_identifier == 0 - assert len(message) == SecretRequest.size - message.secrethash = b"\1" - assert message.secrethash[-1] == 1 - with pytest.raises(ValueError): # too many bytes - message.secrethash = b"\1" * 50 From feb49b28b46fa0bb10052f2e19fb66d68d056e00 Mon Sep 17 00:00:00 2001 From: Karl Bartel Date: Mon, 24 Jun 2019 18:16:46 +0200 Subject: [PATCH 05/19] Simplify hashing for `Unlock` --- raiden/encoding/messages.py | 21 --------------------- raiden/messages.py | 19 +++++++++++++++++++ 2 files changed, 19 insertions(+), 21 deletions(-) diff --git a/raiden/encoding/messages.py b/raiden/encoding/messages.py index be1e12cec3..1db9c44c5a 100644 --- a/raiden/encoding/messages.py +++ b/raiden/encoding/messages.py @@ -66,26 +66,6 @@ def cmdid(id_): non_closing_signature = make_field("non_closing_signature", 65, "65s") reward_proof_signature = make_field("reward_proof_signature", 65, "65s") - -Unlock = namedbuffer( - "unlock", - [ - cmdid(UNLOCK), - pad(3), - chain_id, - message_identifier, - payment_identifier, - token_network_address, - secret, - nonce, - channel_identifier, - transferred_amount, - locked_amount, - locksroot, - signature, - ], -) - RevealSecret = namedbuffer( "reveal_secret", [cmdid(REVEALSECRET), pad(3), message_identifier, secret, signature] ) @@ -211,7 +191,6 @@ def cmdid(id_): CMDID_MESSAGE = { - UNLOCK: Unlock, REVEALSECRET: RevealSecret, LOCKEDTRANSFER: LockedTransfer, REFUNDTRANSFER: RefundTransfer, diff --git a/raiden/messages.py b/raiden/messages.py index 747dbfeeaa..c3fc062ef3 100644 --- a/raiden/messages.py +++ b/raiden/messages.py @@ -609,6 +609,25 @@ def from_event(cls, event): signature=EMPTY_SIGNATURE, ) + @property + def message_hash(self) -> bytes: + return sha3( + pack_data( + (self.cmdid, "uint8"), + (b"\x00" * 3, "bytes"), # padding + (self.chain_id, "uint256"), + (self.message_identifier, "uint64"), + (self.payment_identifier, "uint64"), + (self.token_network_address, "address"), + (self.secret, "bytes32"), + (self.nonce, "uint64"), + (self.channel_identifier, "uint256"), + (self.transferred_amount, "uint256"), + (self.locked_amount, "uint256"), + (self.locksroot, "bytes32"), + ) + ) + @dataclass(repr=False, eq=False) class RevealSecret(SignedRetrieableMessage): From 81dd8144a461dd433e318fcaff32f098191c0c70 Mon Sep 17 00:00:00 2001 From: Karl Bartel Date: Tue, 25 Jun 2019 10:11:05 +0200 Subject: [PATCH 06/19] Fix bad padding --- raiden/messages.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/raiden/messages.py b/raiden/messages.py index c3fc062ef3..9eeaf3702a 100644 --- a/raiden/messages.py +++ b/raiden/messages.py @@ -392,7 +392,7 @@ def from_event(cls, event): def _data_to_sign(self) -> bytes: return pack_data( (self.cmdid, "uint8"), - (b"0" * 3, "bytes"), # padding + (b"\x00" * 3, "bytes"), # padding (self.message_identifier, "uint64"), ) @@ -420,7 +420,7 @@ def __eq__(self, other): def _data_to_sign(self) -> bytes: return pack_data( (self.cmdid, "uint8"), - (b"0" * 3, "bytes"), # padding + (b"\x00" * 3, "bytes"), # padding (self.message_identifier, "uint64"), ) @@ -452,7 +452,7 @@ def __eq__(self, other): def _data_to_sign(self) -> bytes: return pack_data( (self.cmdid, "uint8"), - (b"0" * 3, "bytes"), # padding + (b"\x00" * 3, "bytes"), # padding (self.delivered_message_identifier, "uint64"), ) @@ -474,9 +474,7 @@ def __eq__(self, other): ) def _data_to_sign(self) -> bytes: - return pack_data( - (self.cmdid, "uint8"), (b"0" * 3, "bytes"), (self.nonce, "uint64") # padding - ) + return pack_data((self.cmdid, "uint8"), (b"\x00" * 3, "bytes"), (self.nonce, "uint64")) @dataclass(repr=False, eq=False) @@ -499,7 +497,7 @@ def __eq__(self, other): def _data_to_sign(self) -> bytes: return pack_data( (self.cmdid, "uint8"), - (b"0" * 3, "bytes"), # padding + (b"\x00" * 3, "bytes"), # padding (self.nonce, "uint64"), (self.current_protocol_version, "uint8"), ) @@ -531,7 +529,7 @@ def from_event(cls, event): def _data_to_sign(self) -> bytes: return pack_data( (self.cmdid, "uint8"), - (b"0" * 3, "bytes"), # padding + (b"\x00" * 3, "bytes"), # padding (self.message_identifier, "uint64"), (self.payment_identifier, "uint64"), (self.secrethash, "bytes32"), From 5bfc9af5bc56bfbf0eb2c26a0e91cc11cc4ab0ab Mon Sep 17 00:00:00 2001 From: Karl Bartel Date: Tue, 25 Jun 2019 10:12:15 +0200 Subject: [PATCH 07/19] Simplify signing for reveal_secret --- raiden/encoding/messages.py | 5 ----- raiden/messages.py | 8 ++++++++ 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/raiden/encoding/messages.py b/raiden/encoding/messages.py index 1db9c44c5a..c632a0dabb 100644 --- a/raiden/encoding/messages.py +++ b/raiden/encoding/messages.py @@ -66,10 +66,6 @@ def cmdid(id_): non_closing_signature = make_field("non_closing_signature", 65, "65s") reward_proof_signature = make_field("reward_proof_signature", 65, "65s") -RevealSecret = namedbuffer( - "reveal_secret", [cmdid(REVEALSECRET), pad(3), message_identifier, secret, signature] -) - LockedTransfer = namedbuffer( "mediated_transfer", [ @@ -191,7 +187,6 @@ def cmdid(id_): CMDID_MESSAGE = { - REVEALSECRET: RevealSecret, LOCKEDTRANSFER: LockedTransfer, REFUNDTRANSFER: RefundTransfer, LOCKEXPIRED: LockExpired, diff --git a/raiden/messages.py b/raiden/messages.py index 9eeaf3702a..bee17ea9a9 100644 --- a/raiden/messages.py +++ b/raiden/messages.py @@ -652,6 +652,14 @@ def from_event(cls, event): signature=EMPTY_SIGNATURE, ) + def _data_to_sign(self) -> bytes: + return pack_data( + (self.cmdid, "uint8"), + (b"\x00" * 3, "bytes"), # padding + (self.message_identifier, "uint64"), + (self.secret, "bytes32"), + ) + @dataclass(repr=False, eq=False) class WithdrawRequest(SignedRetrieableMessage): From cc6653c425d4b5741af87f6b2241d815302e5f0f Mon Sep 17 00:00:00 2001 From: Karl Bartel Date: Tue, 25 Jun 2019 10:18:27 +0200 Subject: [PATCH 08/19] Simplify signing for `WithdrawRequest` --- raiden/encoding/messages.py | 15 --------------- raiden/messages.py | 21 ++++++++++++--------- 2 files changed, 12 insertions(+), 24 deletions(-) diff --git a/raiden/encoding/messages.py b/raiden/encoding/messages.py index c632a0dabb..b8db4e9337 100644 --- a/raiden/encoding/messages.py +++ b/raiden/encoding/messages.py @@ -158,20 +158,6 @@ def cmdid(id_): ) -WithdrawRequest = namedbuffer( - "withdraw_request", - [ - token_network_address, - chain_id, - message_type, - channel_identifier, - participant, - total_withdraw, - signature, - ], -) - - Withdraw = namedbuffer( "withdraw", [ @@ -190,7 +176,6 @@ def cmdid(id_): LOCKEDTRANSFER: LockedTransfer, REFUNDTRANSFER: RefundTransfer, LOCKEXPIRED: LockExpired, - WITHDRAW_REQUEST: WithdrawRequest, WITHDRAW: Withdraw, } diff --git a/raiden/messages.py b/raiden/messages.py index bee17ea9a9..32d2a5cb3f 100644 --- a/raiden/messages.py +++ b/raiden/messages.py @@ -666,6 +666,7 @@ class WithdrawRequest(SignedRetrieableMessage): """ Requests a signed on-chain withdraw confirmation from partner. """ cmdid: ClassVar[int] = messages.WITHDRAW_REQUEST + message_type: ClassVar[int] = MessageTypeId.WITHDRAW chain_id: ChainID token_network_address: TokenNetworkAddress @@ -674,15 +675,6 @@ class WithdrawRequest(SignedRetrieableMessage): total_withdraw: WithdrawAmount nonce: Nonce - def pack(self, packed): - packed.chain_id = self.chain_id - packed.token_network_address = self.token_network_address - packed.channel_identifier = self.channel_identifier - packed.total_withdraw = self.total_withdraw - packed.participant = self.participant - packed.message_type = MessageTypeId.WITHDRAW - packed.signature = self.signature - @classmethod def from_event(cls, event): return cls( @@ -696,6 +688,17 @@ def from_event(cls, event): signature=EMPTY_SIGNATURE, ) + def _data_to_sign(self) -> bytes: + return pack_data( + # TODO: should we add cmdid? + (self.token_network_address, "address"), + (self.chain_id, "uint256"), + (self.message_type, "uint256"), + (self.channel_identifier, "uint256"), + (self.participant, "address"), + (self.total_withdraw, "uint256"), + ) + @dataclass(repr=False, eq=False) class Withdraw(SignedRetrieableMessage): From 41199d8884fea38cd4629aeafef4afe57a5ded8d Mon Sep 17 00:00:00 2001 From: Karl Bartel Date: Tue, 25 Jun 2019 11:36:32 +0200 Subject: [PATCH 09/19] Simplify signing for `Withdraw` --- raiden/encoding/messages.py | 15 --------------- raiden/messages.py | 21 ++++++++++++--------- 2 files changed, 12 insertions(+), 24 deletions(-) diff --git a/raiden/encoding/messages.py b/raiden/encoding/messages.py index b8db4e9337..79920f8a3c 100644 --- a/raiden/encoding/messages.py +++ b/raiden/encoding/messages.py @@ -158,25 +158,10 @@ def cmdid(id_): ) -Withdraw = namedbuffer( - "withdraw", - [ - token_network_address, - chain_id, - message_type, - channel_identifier, - participant, - total_withdraw, - signature, - ], -) - - CMDID_MESSAGE = { LOCKEDTRANSFER: LockedTransfer, REFUNDTRANSFER: RefundTransfer, LOCKEXPIRED: LockExpired, - WITHDRAW: Withdraw, } diff --git a/raiden/messages.py b/raiden/messages.py index 32d2a5cb3f..0e672e9b19 100644 --- a/raiden/messages.py +++ b/raiden/messages.py @@ -705,6 +705,7 @@ class Withdraw(SignedRetrieableMessage): """ Confirms withdraw to partner with a signature """ cmdid: ClassVar[int] = messages.WITHDRAW + message_type: ClassVar[int] = MessageTypeId.WITHDRAW chain_id: ChainID token_network_address: TokenNetworkAddress @@ -713,15 +714,6 @@ class Withdraw(SignedRetrieableMessage): total_withdraw: WithdrawAmount nonce: Nonce - def pack(self, packed): - packed.chain_id = self.chain_id - packed.token_network_address = self.token_network_address - packed.channel_identifier = self.channel_identifier - packed.total_withdraw = self.total_withdraw - packed.participant = self.participant - packed.message_type = MessageTypeId.WITHDRAW - packed.signature = self.signature - @classmethod def from_event(cls, event): return cls( @@ -735,6 +727,17 @@ def from_event(cls, event): signature=EMPTY_SIGNATURE, ) + def _data_to_sign(self) -> bytes: + return pack_data( + # TODO: should we add cmdid? + (self.token_network_address, "address"), + (self.chain_id, "uint256"), + (self.message_type, "uint256"), + (self.channel_identifier, "uint256"), + (self.participant, "address"), + (self.total_withdraw, "uint256"), + ) + @dataclass(repr=False, eq=False) class Lock: From 5c4ce4ae78e7aa1872154e0a9643081cc7c8d044 Mon Sep 17 00:00:00 2001 From: Karl Bartel Date: Tue, 25 Jun 2019 11:53:07 +0200 Subject: [PATCH 10/19] `RequestMonitoring`: no extra packing for `__eq__` --- raiden/encoding/messages.py | 17 ---------------- raiden/messages.py | 39 +++++++++++++++---------------------- 2 files changed, 16 insertions(+), 40 deletions(-) diff --git a/raiden/encoding/messages.py b/raiden/encoding/messages.py index 79920f8a3c..c0bdf23373 100644 --- a/raiden/encoding/messages.py +++ b/raiden/encoding/messages.py @@ -141,23 +141,6 @@ def cmdid(id_): Lock = namedbuffer("lock", [expiration, amount, secrethash]) -RequestMonitoring = namedbuffer( - "request_monitoring", - [ - nonce, - chain_id, - token_network_address, - channel_identifier, - balance_hash, - additional_hash, - signature, - non_closing_signature, - reward_amount, - reward_proof_signature, - ], -) - - CMDID_MESSAGE = { LOCKEDTRANSFER: LockedTransfer, REFUNDTRANSFER: RefundTransfer, diff --git a/raiden/messages.py b/raiden/messages.py index 0e672e9b19..f462675979 100644 --- a/raiden/messages.py +++ b/raiden/messages.py @@ -1141,6 +1141,16 @@ class RequestMonitoring(SignedMessage): def __post_init__(self): typecheck(self.balance_proof, SignedBlindedBalanceProof) + # TODO: Can this be moved to SignedMessage? We have two signatures. + # Or can we fall back to normal dataclasses comparison? + def __eq__(self, other): + return ( + isinstance(other, self.__class__) + and self._data_to_sign() == other._data_to_sign() + and self.signature == other.signature + and self.non_closing_signature == other.non_closing_signature + ) + @classmethod def from_balance_proof_signed_state( cls, @@ -1168,6 +1178,11 @@ def reward_proof_signature(self) -> Optional[Signature]: def _data_to_sign(self) -> bytes: """ Return the binary data to be/which was signed """ + if self.non_closing_signature is None: + raise ValueError("non_closing_signature missing, did you forget to sign()?") + if self.reward_proof_signature is None: + raise ValueError("reward_proof_signature missing, did you forget to sign()?") + packed = pack_reward_proof( canonical_identifier=CanonicalIdentifier( chain_identifier=self.balance_proof.chain_id, @@ -1178,6 +1193,7 @@ def _data_to_sign(self) -> bytes: nonce=self.balance_proof.nonce, monitoring_service_contract_address=self.monitoring_service_contract_address, ) + # TODO: should we add cmdid? return packed def sign(self, signer: Signer): @@ -1189,29 +1205,6 @@ def sign(self, signer: Signer): message_data = self._data_to_sign() self.signature = signer.sign(data=message_data) - def packed(self) -> bytes: - klass = messages.RequestMonitoring - data = buffer_for(klass) - packed = klass(data) - self.pack(packed) - return packed - - def pack(self, packed) -> None: - if self.non_closing_signature is None: - raise ValueError("non_closing_signature missing, did you forget to sign()?") - if self.reward_proof_signature is None: - raise ValueError("reward_proof_signature missing, did you forget to sign()?") - packed.nonce = self.balance_proof.nonce - packed.chain_id = self.balance_proof.chain_id - packed.token_network_address = self.balance_proof.token_network_address - packed.channel_identifier = self.balance_proof.channel_identifier - packed.balance_hash = self.balance_proof.balance_hash - packed.additional_hash = self.balance_proof.additional_hash - packed.signature = self.balance_proof.signature - packed.non_closing_signature = self.non_closing_signature - packed.reward_amount = self.reward_amount - packed.reward_proof_signature = self.reward_proof_signature - def verify_request_monitoring( self, partner_address: Address, requesting_address: Address ) -> bool: From 9794fe80b0cbfd27db220511bd0cb8c4b930aaa8 Mon Sep 17 00:00:00 2001 From: Karl Bartel Date: Tue, 25 Jun 2019 12:08:17 +0200 Subject: [PATCH 11/19] Simplify signing of `LockedTransfer` --- raiden/encoding/messages.py | 31 +---------- raiden/messages.py | 68 +++++++++++------------ raiden/tests/unit/test_binary_encoding.py | 2 +- 3 files changed, 35 insertions(+), 66 deletions(-) diff --git a/raiden/encoding/messages.py b/raiden/encoding/messages.py index c0bdf23373..c0b029b476 100644 --- a/raiden/encoding/messages.py +++ b/raiden/encoding/messages.py @@ -66,31 +66,6 @@ def cmdid(id_): non_closing_signature = make_field("non_closing_signature", 65, "65s") reward_proof_signature = make_field("reward_proof_signature", 65, "65s") -LockedTransfer = namedbuffer( - "mediated_transfer", - [ - cmdid(LOCKEDTRANSFER), - pad(3), - nonce, - chain_id, - message_identifier, - payment_identifier, - expiration, - token_network_address, - token, - channel_identifier, - recipient, - target, - initiator, - locksroot, - secrethash, - transferred_amount, - locked_amount, - amount, - fee, - signature, - ], -) RefundTransfer = namedbuffer( "refund_transfer", @@ -141,11 +116,7 @@ def cmdid(id_): Lock = namedbuffer("lock", [expiration, amount, secrethash]) -CMDID_MESSAGE = { - LOCKEDTRANSFER: LockedTransfer, - REFUNDTRANSFER: RefundTransfer, - LOCKEXPIRED: LockExpired, -} +CMDID_MESSAGE = {REFUNDTRANSFER: RefundTransfer, LOCKEXPIRED: LockExpired} def wrap(data): diff --git a/raiden/messages.py b/raiden/messages.py index f462675979..12ba8a1720 100644 --- a/raiden/messages.py +++ b/raiden/messages.py @@ -901,43 +901,41 @@ def __post_init__(self): if self.fee > UINT256_MAX: raise ValueError("fee is too large") + # TODO: move to SignedMessage + def __eq__(self, other): + return ( + isinstance(other, self.__class__) + and self._data_to_sign() == other._data_to_sign() + and self.signature == other.signature + ) + @property - def message_hash(self): + def message_hash(self) -> bytes: metadata_hash = (self.metadata and self.metadata.hash) or b"" - packed = self.packed() - klass = type(packed) - - field = klass.fields_spec[-1] - assert field.name == "signature", "signature is not the last field" - - data = packed.data - message_data = data[: -field.size_bytes] - message_hash = sha3(message_data + metadata_hash) - - return message_hash - - def pack(self, packed) -> None: - packed.chain_id = self.chain_id - packed.message_identifier = self.message_identifier - packed.payment_identifier = self.payment_identifier - packed.nonce = self.nonce - packed.token_network_address = self.token_network_address - packed.token = self.token - packed.channel_identifier = self.channel_identifier - packed.transferred_amount = self.transferred_amount - packed.locked_amount = self.locked_amount - packed.recipient = self.recipient - packed.locksroot = self.locksroot - packed.target = self.target - packed.initiator = self.initiator - packed.fee = self.fee - - lock = self.lock - packed.amount = lock.amount - packed.expiration = lock.expiration - packed.secrethash = lock.secrethash - - packed.signature = self.signature + return sha3( + pack_data( + (self.cmdid, "uint8"), + (b"\x00" * 3, "bytes"), # padding + (self.nonce, "uint64"), + (self.chain_id, "uint256"), + (self.message_identifier, "uint64"), + (self.payment_identifier, "uint64"), + (self.lock.expiration, "uint256"), + (self.token_network_address, "address"), + (self.token, "address"), + (self.channel_identifier, "uint256"), + (self.recipient, "address"), + (self.target, "address"), + (self.initiator, "address"), + (self.locksroot, "bytes32"), + (self.lock.secrethash, "bytes32"), + (self.transferred_amount, "uint256"), + (self.locked_amount, "uint256"), + (self.lock.amount, "uint256"), + (self.fee, "uint256"), + ) + + metadata_hash + ) @classmethod def from_event(cls, event: SendLockedTransfer) -> "LockedTransfer": diff --git a/raiden/tests/unit/test_binary_encoding.py b/raiden/tests/unit/test_binary_encoding.py index b8312a26af..ef360bee1e 100644 --- a/raiden/tests/unit/test_binary_encoding.py +++ b/raiden/tests/unit/test_binary_encoding.py @@ -86,7 +86,7 @@ def test_mediated_transfer_min_max(amount, payment_identifier, fee, nonce, trans fee=fee, ) ) - mediated_transfer.packed() # Just test that packing works without exceptions. + mediated_transfer._data_to_sign() # Just test that packing works without exceptions. @pytest.mark.parametrize("amount", [0, constants.UINT256_MAX]) From 134adce24b8e0d9d9d0a09e55a9efffd50cf421c Mon Sep 17 00:00:00 2001 From: Karl Bartel Date: Tue, 25 Jun 2019 12:19:04 +0200 Subject: [PATCH 12/19] Simplify signing for `RefundTransfer` --- raiden/encoding/messages.py | 29 +---------------------- raiden/messages.py | 28 ++++++++++++++++++++++ raiden/tests/unit/test_binary_encoding.py | 2 +- 3 files changed, 30 insertions(+), 29 deletions(-) diff --git a/raiden/encoding/messages.py b/raiden/encoding/messages.py index c0b029b476..c48ce522f3 100644 --- a/raiden/encoding/messages.py +++ b/raiden/encoding/messages.py @@ -66,33 +66,6 @@ def cmdid(id_): non_closing_signature = make_field("non_closing_signature", 65, "65s") reward_proof_signature = make_field("reward_proof_signature", 65, "65s") - -RefundTransfer = namedbuffer( - "refund_transfer", - [ - cmdid(REFUNDTRANSFER), - pad(3), - nonce, - chain_id, - message_identifier, - payment_identifier, - expiration, - token_network_address, - token, - channel_identifier, - recipient, - target, - initiator, - locksroot, - secrethash, - transferred_amount, - locked_amount, - amount, - fee, - signature, - ], -) - LockExpired = namedbuffer( "lock_expired", [ @@ -116,7 +89,7 @@ def cmdid(id_): Lock = namedbuffer("lock", [expiration, amount, secrethash]) -CMDID_MESSAGE = {REFUNDTRANSFER: RefundTransfer, LOCKEXPIRED: LockExpired} +CMDID_MESSAGE = {LOCKEXPIRED: LockExpired} def wrap(data): diff --git a/raiden/messages.py b/raiden/messages.py index 12ba8a1720..a2780e3730 100644 --- a/raiden/messages.py +++ b/raiden/messages.py @@ -1018,6 +1018,34 @@ def from_event(cls, event): ), ) + @property + def message_hash(self) -> bytes: + # TODO: This is the same as for LockedTransfer except for the metadata. + # Refactor this into something shared. + return sha3( + pack_data( + (self.cmdid, "uint8"), + (b"\x00" * 3, "bytes"), # padding + (self.nonce, "uint64"), + (self.chain_id, "uint256"), + (self.message_identifier, "uint64"), + (self.payment_identifier, "uint64"), + (self.lock.expiration, "uint256"), + (self.token_network_address, "address"), + (self.token, "address"), + (self.channel_identifier, "uint256"), + (self.recipient, "address"), + (self.target, "address"), + (self.initiator, "address"), + (self.locksroot, "bytes32"), + (self.lock.secrethash, "bytes32"), + (self.transferred_amount, "uint256"), + (self.locked_amount, "uint256"), + (self.lock.amount, "uint256"), + (self.fee, "uint256"), + ) + ) + @dataclass(repr=False, eq=False) class LockExpired(EnvelopeMessage): diff --git a/raiden/tests/unit/test_binary_encoding.py b/raiden/tests/unit/test_binary_encoding.py index ef360bee1e..3ff9b36b82 100644 --- a/raiden/tests/unit/test_binary_encoding.py +++ b/raiden/tests/unit/test_binary_encoding.py @@ -102,4 +102,4 @@ def test_refund_transfer_min_max(amount, payment_identifier, nonce, transferred_ transferred_amount=transferred_amount, ) ) - refund_transfer.packed() # Just test that packing works without exceptions. + refund_transfer._data_to_sign() # Just test that packing works without exceptions. From 8e16f2bbabcb23d58712400856f2a9959c7ac66b Mon Sep 17 00:00:00 2001 From: Karl Bartel Date: Tue, 25 Jun 2019 14:06:02 +0200 Subject: [PATCH 13/19] Simplify signing for `LockExpired` --- raiden/encoding/messages.py | 49 +----------------------------- raiden/messages.py | 33 +++++++++++++------- raiden/tests/unit/test_encoding.py | 7 ----- 3 files changed, 23 insertions(+), 66 deletions(-) diff --git a/raiden/encoding/messages.py b/raiden/encoding/messages.py index c48ce522f3..1f5421cd44 100644 --- a/raiden/encoding/messages.py +++ b/raiden/encoding/messages.py @@ -2,8 +2,7 @@ from raiden.constants import UINT64_MAX, UINT256_MAX from raiden.encoding.encoders import integer -from raiden.encoding.format import make_field, namedbuffer, pad -from raiden.exceptions import InvalidProtocolMessage +from raiden.encoding.format import make_field, namedbuffer def cmdid(id_): @@ -66,50 +65,4 @@ def cmdid(id_): non_closing_signature = make_field("non_closing_signature", 65, "65s") reward_proof_signature = make_field("reward_proof_signature", 65, "65s") -LockExpired = namedbuffer( - "lock_expired", - [ - cmdid(LOCKEXPIRED), - pad(3), - nonce, - chain_id, - message_identifier, - token_network_address, - channel_identifier, - recipient, - locksroot, - secrethash, - transferred_amount, - locked_amount, - signature, - ], -) - - Lock = namedbuffer("lock", [expiration, amount, secrethash]) - - -CMDID_MESSAGE = {LOCKEXPIRED: LockExpired} - - -def wrap(data): - """ Try to decode data into a message, might return None if the data is invalid. """ - try: - cmdid = data[0] - except IndexError: - log.warning("data is empty") - return None - - try: - message_type = CMDID_MESSAGE[cmdid] - except KeyError: - log.error("unknown cmdid %s", cmdid) - return None - - try: - message = message_type(data) - except (InvalidProtocolMessage, ValueError): - log.error("trying to decode invalid message") - return None - - return message diff --git a/raiden/messages.py b/raiden/messages.py index a2780e3730..d57938b272 100644 --- a/raiden/messages.py +++ b/raiden/messages.py @@ -5,7 +5,7 @@ import rlp from cachetools import LRUCache, cached -from eth_utils import big_endian_to_int, to_checksum_address, to_hex +from eth_utils import big_endian_to_int, to_checksum_address from raiden.constants import EMPTY_SIGNATURE, UINT64_MAX, UINT256_MAX from raiden.encoding import messages @@ -200,9 +200,7 @@ def __ne__(self, other): return not self.__eq__(other) def __repr__(self): - return "<{klass} [msghash={msghash}]>".format( - klass=self.__class__.__name__, msghash=to_hex(self.hash) - ) + return "<{klass}>".format(klass=self.__class__.__name__) @property def hash(self): @@ -210,13 +208,7 @@ def hash(self): return sha3(packed.data) def packed(self): - klass = messages.CMDID_MESSAGE[self.cmdid] - data = buffer_for(klass) - data[0] = self.cmdid - packed = klass(data) - self.pack(packed) - - return packed + raise NotImplementedError def pack(self, packed) -> None: for f in fields(self): @@ -1089,6 +1081,25 @@ def from_event(cls, event): signature=EMPTY_SIGNATURE, ) + @property + def message_hash(self) -> bytes: + return sha3( + pack_data( + (self.cmdid, "uint8"), + (b"\x00" * 3, "bytes"), # padding + (self.nonce, "uint64"), + (self.chain_id, "uint256"), + (self.message_identifier, "uint64"), + (self.token_network_address, "address"), + (self.channel_identifier, "uint256"), + (self.recipient, "address"), + (self.locksroot, "bytes32"), + (self.secrethash, "bytes32"), + (self.transferred_amount, "uint256"), + (self.locked_amount, "uint256"), + ) + ) + @dataclass(repr=False, eq=False) class SignedBlindedBalanceProof: diff --git a/raiden/tests/unit/test_encoding.py b/raiden/tests/unit/test_encoding.py index 625cd48e2a..f8007ec2d6 100644 --- a/raiden/tests/unit/test_encoding.py +++ b/raiden/tests/unit/test_encoding.py @@ -2,7 +2,6 @@ from raiden.encoding.encoders import integer from raiden.encoding.format import Field, make_field, namedbuffer -from raiden.encoding.messages import SECRETREQUEST, wrap def test_integer_encoder(): @@ -37,9 +36,3 @@ def test_named_buffer_invalid_input(): namedbuffer("field_named_data", [field_named_data, valid_field]) with pytest.raises(ValueError): namedbuffer("repeated_field_name", [valid_field, valid_field]) - - -def test_wrap_invalid(): - assert wrap(data=[]) is None - assert wrap(data=[10000]) is None, "Unknown cmdid" - assert wrap(data=[SECRETREQUEST]) is None, "Length not equal to SecretRequest.size" From 612550787fea34308b157c5f62fc628eb0472549 Mon Sep 17 00:00:00 2001 From: Karl Bartel Date: Tue, 25 Jun 2019 14:36:59 +0200 Subject: [PATCH 14/19] Simplify packing for `Lock` --- raiden/encoding/messages.py | 4 +--- raiden/messages.py | 18 ++++++------------ raiden/transfer/state.py | 11 +++-------- 3 files changed, 10 insertions(+), 23 deletions(-) diff --git a/raiden/encoding/messages.py b/raiden/encoding/messages.py index 1f5421cd44..dd084fc1d8 100644 --- a/raiden/encoding/messages.py +++ b/raiden/encoding/messages.py @@ -2,7 +2,7 @@ from raiden.constants import UINT64_MAX, UINT256_MAX from raiden.encoding.encoders import integer -from raiden.encoding.format import make_field, namedbuffer +from raiden.encoding.format import make_field def cmdid(id_): @@ -64,5 +64,3 @@ def cmdid(id_): signature = make_field("signature", 65, "65s") non_closing_signature = make_field("non_closing_signature", 65, "65s") reward_proof_signature = make_field("reward_proof_signature", 65, "65s") - -Lock = namedbuffer("lock", [expiration, amount, secrethash]) diff --git a/raiden/messages.py b/raiden/messages.py index d57938b272..ec5f750660 100644 --- a/raiden/messages.py +++ b/raiden/messages.py @@ -9,7 +9,6 @@ from raiden.constants import EMPTY_SIGNATURE, UINT64_MAX, UINT256_MAX from raiden.encoding import messages -from raiden.encoding.format import buffer_for from raiden.exceptions import InvalidSignature from raiden.storage.serialization import DictSerializer from raiden.transfer import channel @@ -769,13 +768,9 @@ def __post_init__(self): @property # type: ignore @cached(_lock_bytes_cache, key=attrgetter("amount", "expiration", "secrethash")) def as_bytes(self): - packed = messages.Lock(buffer_for(messages.Lock)) - packed.amount = self.amount - packed.expiration = self.expiration - packed.secrethash = self.secrethash - - # convert bytearray to bytes - return bytes(packed.data) + return pack_data( + (self.expiration, "uint256"), (self.amount, "uint256"), (self.secrethash, "bytes32") + ) @property # type: ignore @cached(_hashes_cache, key=attrgetter("as_bytes")) @@ -784,11 +779,10 @@ def lockhash(self): @classmethod def from_bytes(cls, serialized): - packed = messages.Lock(serialized) - - # pylint: disable=unexpected-keyword-arg return cls( - amount=packed.amount, expiration=packed.expiration, secrethash=packed.secrethash + expiration=int.from_bytes(serialized[:32], byteorder="big"), + amount=int.from_bytes(serialized[32:64], byteorder="big"), + secrethash=serialized[64:], ) diff --git a/raiden/transfer/state.py b/raiden/transfer/state.py index a7ed58a73d..6d523c2c3c 100644 --- a/raiden/transfer/state.py +++ b/raiden/transfer/state.py @@ -9,8 +9,6 @@ from eth_utils import to_checksum_address, to_hex from raiden.constants import EMPTY_SECRETHASH, LOCKSROOT_OF_NO_LOCKS, UINT64_MAX, UINT256_MAX -from raiden.encoding import messages -from raiden.encoding.format import buffer_for from raiden.transfer.architecture import ( BalanceProofSignedState, BalanceProofUnsignedState, @@ -198,13 +196,10 @@ def __post_init__(self) -> None: typecheck(self.expiration, T_BlockNumber) typecheck(self.secrethash, T_Secret) - packed = messages.Lock(buffer_for(messages.Lock)) - # pylint: disable=assigning-non-slot - packed.amount = self.amount - packed.expiration = self.expiration - packed.secrethash = self.secrethash + from raiden.messages import Lock # put here to avoid cyclic depenendcies - self.encoded = EncodedData(packed.data) + lock = Lock(amount=self.amount, expiration=self.expiration, secrethash=self.secrethash) + self.encoded = EncodedData(lock.as_bytes) @dataclass From 30f5f36ecdb112976cd9173113d2dd4c5769014c Mon Sep 17 00:00:00 2001 From: Karl Bartel Date: Tue, 25 Jun 2019 15:01:03 +0200 Subject: [PATCH 15/19] Remove unused `raiden.encoding` code --- raiden/encoding/encoders.py | 29 ----- raiden/encoding/format.py | 164 -------------------------- raiden/encoding/messages.py | 53 --------- raiden/messages.py | 42 +------ raiden/tests/unit/test_encoding.py | 38 ------ raiden/tests/unit/test_namedbuffer.py | 67 ----------- 6 files changed, 4 insertions(+), 389 deletions(-) delete mode 100644 raiden/encoding/encoders.py delete mode 100644 raiden/encoding/format.py delete mode 100644 raiden/tests/unit/test_encoding.py delete mode 100644 raiden/tests/unit/test_namedbuffer.py diff --git a/raiden/encoding/encoders.py b/raiden/encoding/encoders.py deleted file mode 100644 index 394cbf14c8..0000000000 --- a/raiden/encoding/encoders.py +++ /dev/null @@ -1,29 +0,0 @@ -from raiden.utils.typing import typecheck - -__all__ = ("integer",) - - -class integer: # pylint: disable=invalid-name - """ Defines the value as an integer and it's valid value range. """ - - def __init__(self, minimum: int, maximum: int): - self.minimum = minimum - self.maximum = maximum - - def validate(self, value: int): - """ Validates the integer is in the value range. """ - typecheck(value, int) - - if self.minimum > value or self.maximum < value: - msg = ("{} is outside the valide range [{},{}]").format( - value, self.minimum, self.maximum - ) - raise ValueError(msg) - - @staticmethod - def encode(value: int, length: int): - return value.to_bytes(length, byteorder="big") - - @staticmethod - def decode(value: bytes): - return int.from_bytes(value, byteorder="big") # pylint: disable=no-member diff --git a/raiden/encoding/format.py b/raiden/encoding/format.py deleted file mode 100644 index 764c5e62ac..0000000000 --- a/raiden/encoding/format.py +++ /dev/null @@ -1,164 +0,0 @@ -from collections import Counter, namedtuple - -from raiden.exceptions import InvalidProtocolMessage - -__all__ = ("Field", "namedbuffer", "buffer_for") - - -Field = namedtuple("Field", ("name", "size_bytes", "format_string", "encoder")) - -Pad = namedtuple("Pad", ("size_bytes", "format_string")) - - -def make_field(name, size_bytes, format_string, encoder=None): - if size_bytes < 0: - raise ValueError("negative size_bytes") - - return Field(name, size_bytes, format_string, encoder) - - -def pad(bytes_): - return Pad(bytes_, f"{bytes_}x") - - -def buffer_for(klass): - """ Returns a new buffer of the appropriate size for klass. """ - return bytearray(klass.size) - - -def compute_slices(fields_spec): - name_to_slice = dict() - start = 0 - - for field in fields_spec: - end = start + field.size_bytes - - if not isinstance(field, Pad): # do not create slices for paddings - name_to_slice[field.name] = slice(start, end) - - start = end - - return name_to_slice - - -def namedbuffer(buffer_name, fields_spec): # noqa (ignore ciclomatic complexity) - """ Class factory, returns a class to wrap a buffer instance and expose the - data as fields. - - The field spec specifies how many bytes should be used for a field and what - is the encoding / decoding function. - """ - # pylint: disable=protected-access,unused-argument - - if not len(buffer_name): - raise ValueError("buffer_name is empty") - - if not len(fields_spec): - raise ValueError("fields_spec is empty") - - fields = [field for field in fields_spec if not isinstance(field, Pad)] - - if any(field.size_bytes < 0 for field in fields): - raise ValueError("negative size_bytes") - - if any(len(field.name) == 0 for field in fields): - raise ValueError("field missing name") - - names_fields = {field.name: field for field in fields} - - if "data" in names_fields: - raise ValueError("data field shadowing underlying buffer") - - if any(count > 1 for count in Counter(field.name for field in fields).values()): - raise ValueError("repeated field name") - - # big endian format - fields_format = ">" + "".join(field.format_string for field in fields_spec) - size = sum(field.size_bytes for field in fields_spec) - names_slices = compute_slices(fields_spec) - sorted_names = sorted(names_fields.keys()) - - @staticmethod # type: ignore - def get_bytes_from(buffer_, name): - slice_ = names_slices[name] - return buffer_[slice_] - - def __init__(self, data): - if len(data) < size: - raise InvalidProtocolMessage(f"data buffer has less than the expected size {size}") - - object.__setattr__(self, "data", data) - - # Intentionally exposing only the attributes from the spec, since the idea - # is for the instance to expose the underlying buffer as attributes - def __getattribute__(self, name): - if name in names_slices: - slice_ = names_slices[name] - field = names_fields[name] - - data = object.__getattribute__(self, "data") - value = data[slice_] - - if field.encoder: - value = field.encoder.decode(value) - - return value - - if name == "data": - return object.__getattribute__(self, "data") - - raise AttributeError - - def __setattr__(self, name, value): - if name in names_slices: - slice_ = names_slices[name] - field = names_fields[name] - - if field.encoder: - field.encoder.validate(value) - value = field.encoder.encode(value, field.size_bytes) - - length = len(value) - if length > field.size_bytes: - msg = "value with length {length} for {attr} is too big".format( - length=length, attr=name - ) - raise ValueError(msg) - elif length < field.size_bytes: - pad_size = field.size_bytes - length - pad_value = b"\x00" * pad_size - value = pad_value + value - - data = object.__getattribute__(self, "data") - if isinstance(value, str): - value = value.encode() - data[slice_] = value - else: - super(self.__class__, self).__setattr__(name, value) # type: ignore - - def __repr__(self): - return f"<{buffer_name} [...]>" - - def __len__(self): - return size - - def __dir__(self): - return sorted_names - - attributes = { - "__init__": __init__, - "__slots__": ("data",), - "__getattribute__": __getattribute__, - "__setattr__": __setattr__, - "__repr__": __repr__, - "__len__": __len__, - "__dir__": __dir__, - # These are class attributes hidden from instance, i.e. must be - # accessed through the class instance. - "fields_spec": fields_spec, - "format": fields_format, - "size": size, - "get_bytes_from": get_bytes_from, - } - - return type(buffer_name, (), attributes) diff --git a/raiden/encoding/messages.py b/raiden/encoding/messages.py index dd084fc1d8..af67443d23 100644 --- a/raiden/encoding/messages.py +++ b/raiden/encoding/messages.py @@ -1,14 +1,3 @@ -import structlog - -from raiden.constants import UINT64_MAX, UINT256_MAX -from raiden.encoding.encoders import integer -from raiden.encoding.format import make_field - - -def cmdid(id_): - return make_field("cmdid", 1, "B", integer(id_, id_)) - - PROCESSED = 0 PING = 1 PONG = 2 @@ -22,45 +11,3 @@ def cmdid(id_): TODEVICE = 14 WITHDRAW_REQUEST = 15 WITHDRAW = 16 - - -log = structlog.get_logger(__name__) - - -nonce = make_field("nonce", 8, "8s", integer(0, UINT64_MAX)) -updating_nonce = make_field("updating_nonce", 8, "8s", integer(0, UINT64_MAX)) -other_nonce = make_field("other_nonce", 8, "8s", integer(0, UINT64_MAX)) -payment_identifier = make_field("payment_identifier", 8, "8s", integer(0, UINT64_MAX)) -chain_id = make_field("chain_id", 32, "32s", integer(0, UINT256_MAX)) -message_identifier = make_field("message_identifier", 8, "8s", integer(0, UINT64_MAX)) -expiration = make_field("expiration", 32, "32s", integer(0, UINT256_MAX)) - -token_network_address = make_field("token_network_address", 20, "20s") -token = make_field("token", 20, "20s") -recipient = make_field("recipient", 20, "20s") -target = make_field("target", 20, "20s") -initiator = make_field("initiator", 20, "20s") -participant = make_field("participant", 20, "20s") -updating_participant = make_field("updating_participant", 20, "20s") -other_participant = make_field("other_participant", 20, "20s") -channel_identifier = make_field("channel_identifier", 32, "32s", integer(0, UINT256_MAX)) - -locksroot = make_field("locksroot", 32, "32s") -secrethash = make_field("secrethash", 32, "32s") -balance_hash = make_field("balance_hash", 32, "32s") -additional_hash = make_field("additional_hash", 32, "32s") -secret = make_field("secret", 32, "32s") -transferred_amount = make_field("transferred_amount", 32, "32s", integer(0, UINT256_MAX)) -locked_amount = make_field("locked_amount", 32, "32s", integer(0, UINT256_MAX)) -amount = make_field("amount", 32, "32s", integer(0, UINT256_MAX)) -reward_amount = make_field("reward_amount", 32, "32s", integer(0, UINT256_MAX)) -fee = make_field("fee", 32, "32s", integer(0, UINT256_MAX)) -total_withdraw = make_field("total_withdraw", 32, "32s", integer(0, UINT256_MAX)) -reveal_timeout = make_field("reveal_timeout", 32, "32s", integer(0, UINT256_MAX)) -updating_capacity = make_field("updating_capacity", 32, "32s", integer(0, UINT256_MAX)) -other_capacity = make_field("other_capacity", 32, "32s", integer(0, UINT256_MAX)) -message_type = make_field("message_type", 32, "32s", integer(0, UINT256_MAX)) - -signature = make_field("signature", 65, "65s") -non_closing_signature = make_field("non_closing_signature", 65, "65s") -reward_proof_signature = make_field("reward_proof_signature", 65, "65s") diff --git a/raiden/messages.py b/raiden/messages.py index ec5f750660..41fbfc82db 100644 --- a/raiden/messages.py +++ b/raiden/messages.py @@ -1,11 +1,11 @@ -from dataclasses import dataclass, field, fields +from dataclasses import dataclass, field from datetime import datetime, timezone from hashlib import sha256 from operator import attrgetter import rlp from cachetools import LRUCache, cached -from eth_utils import big_endian_to_int, to_checksum_address +from eth_utils import to_checksum_address from raiden.constants import EMPTY_SIGNATURE, UINT64_MAX, UINT256_MAX from raiden.encoding import messages @@ -189,30 +189,12 @@ class Message: # Needs to be set by a subclass cmdid: ClassVar[int] - def __eq__(self, other): - return isinstance(other, self.__class__) and self.hash == other.hash - - def __hash__(self): - return big_endian_to_int(self.hash) - def __ne__(self, other): return not self.__eq__(other) def __repr__(self): return "<{klass}>".format(klass=self.__class__.__name__) - @property - def hash(self): - packed = self.packed() - return sha3(packed.data) - - def packed(self): - raise NotImplementedError - - def pack(self, packed) -> None: - for f in fields(self): - setattr(packed, f.name, getattr(self, f.name)) - @dataclass(repr=False, eq=False) class AuthenticatedMessage(Message): @@ -231,13 +213,7 @@ class SignedMessage(AuthenticatedMessage): def _data_to_sign(self) -> bytes: """ Return the binary data to be/which was signed """ - packed = self.packed() - - field = type(packed).fields_spec[-1] - assert field.name == "signature", "signature is not the last field" - - # this slice must be from the end of the buffer - return packed.data[: -field.size_bytes] + raise NotImplementedError def sign(self, signer: Signer): """ Sign message using signer. """ @@ -303,17 +279,7 @@ def __post_init__(self): @property def message_hash(self): - packed = self.packed() - klass = type(packed) - - field = klass.fields_spec[-1] - assert field.name == "signature", "signature is not the last field" - - data = packed.data - message_data = data[: -field.size_bytes] - message_hash = sha3(message_data) - - return message_hash + raise NotImplementedError def _data_to_sign(self) -> bytes: balance_hash = hash_balance_data( diff --git a/raiden/tests/unit/test_encoding.py b/raiden/tests/unit/test_encoding.py deleted file mode 100644 index f8007ec2d6..0000000000 --- a/raiden/tests/unit/test_encoding.py +++ /dev/null @@ -1,38 +0,0 @@ -import pytest - -from raiden.encoding.encoders import integer -from raiden.encoding.format import Field, make_field, namedbuffer - - -def test_integer_encoder(): - encoder = integer(minimum=10, maximum=100) - for value in (10, 11, 50, 100): - encoder.validate(value) - assert encoder.decode(encoder.encode(value, 8)) == value - for value in (9, 101, 1000): - with pytest.raises(ValueError): - encoder.validate(value) - - -def test_make_field_invalid_input(): - with pytest.raises(ValueError): - make_field(name="name", size_bytes=-5, format_string="{}") - - -def test_named_buffer_invalid_input(): - valid_field = make_field("valid", 4, "{}") - invalid_field1 = Field("invalid", -5, "{}", None) - invalid_field2 = Field("", 4, "{}", None) - field_named_data = make_field("data", 4, "{}") - with pytest.raises(ValueError): - namedbuffer("", [valid_field]) - with pytest.raises(ValueError): - namedbuffer("has_a_name_but_no_fields", []) - with pytest.raises(ValueError): - namedbuffer("has_invalid_field", [valid_field, invalid_field1]) - with pytest.raises(ValueError): - namedbuffer("has_nameless_field", [valid_field, invalid_field2]) - with pytest.raises(ValueError): - namedbuffer("field_named_data", [field_named_data, valid_field]) - with pytest.raises(ValueError): - namedbuffer("repeated_field_name", [valid_field, valid_field]) diff --git a/raiden/tests/unit/test_namedbuffer.py b/raiden/tests/unit/test_namedbuffer.py deleted file mode 100644 index e6deaa832e..0000000000 --- a/raiden/tests/unit/test_namedbuffer.py +++ /dev/null @@ -1,67 +0,0 @@ -import pytest - -from raiden.encoding.encoders import integer -from raiden.encoding.format import Field, namedbuffer - -# pylint: disable=invalid-name -byte = Field("byte", 1, "B", None) -hugeint = Field("huge", 100, "100s", integer(0, 2 ** (8 * 100))) -SingleByte = namedbuffer("SingleByte", [byte]) -HugeInt = namedbuffer("HugeInt", [hugeint]) - - -def test_byte(): - data = bytearray(1) # zero initialized - - packed_data = SingleByte(data) - assert packed_data.byte == b"\x00" - - packed_data.byte = b"\x01" - assert packed_data.byte == b"\x01" - - -def test_decoder_int(): - data = bytearray(100) - - packed_data = HugeInt(data) - assert packed_data.huge == 0 - - packed_data.huge = 1 - assert packed_data.huge == 1 - - -def test_decoder_long(): - data = bytearray(100) - - packed_data = HugeInt(data) - assert packed_data.huge == 0 - - packed_data.huge = 1 - assert packed_data.huge == 1 - - packed_data.huge = 2 ** 32 - assert packed_data.huge == 2 ** 32 - - huge = 2 ** (8 * 100) - 1 - packed_data.huge = huge - assert packed_data.huge == huge - - -def test_namedbuffer_does_not_expose_internals(): - # pylint: disable=pointless-statement - data = bytearray(1) - packed_data = SingleByte(data) - - with pytest.raises(AttributeError): - packed_data.format - - with pytest.raises(AttributeError): - packed_data.fields_spec - - # only byte is exposed - assert dir(packed_data) == ["byte"] - - -def test_namedbuffer_type_exposes_details(): - assert SingleByte.format == ">B" - assert SingleByte.fields_spec == [byte] From a77677888e873b53845c74783cf425c27770b8d2 Mon Sep 17 00:00:00 2001 From: Karl Bartel Date: Tue, 25 Jun 2019 15:29:01 +0200 Subject: [PATCH 16/19] Clean up magic functions in messages --- raiden/messages.py | 94 +++++++++++----------------------------------- 1 file changed, 21 insertions(+), 73 deletions(-) diff --git a/raiden/messages.py b/raiden/messages.py index 41fbfc82db..e3447d5ffa 100644 --- a/raiden/messages.py +++ b/raiden/messages.py @@ -5,7 +5,7 @@ import rlp from cachetools import LRUCache, cached -from eth_utils import to_checksum_address +from eth_utils import to_checksum_address, to_hex from raiden.constants import EMPTY_SIGNATURE, UINT64_MAX, UINT256_MAX from raiden.encoding import messages @@ -189,12 +189,6 @@ class Message: # Needs to be set by a subclass cmdid: ClassVar[int] - def __ne__(self, other): - return not self.__eq__(other) - - def __repr__(self): - return "<{klass}>".format(klass=self.__class__.__name__) - @dataclass(repr=False, eq=False) class AuthenticatedMessage(Message): @@ -211,8 +205,25 @@ class SignedMessage(AuthenticatedMessage): # by changing the order to packing then signing signature: Signature + def __hash__(self): + return hash((self._data_to_sign(), self.signature)) + + def __eq__(self, other): + return isinstance(other, self.__class__) and hash(self) == hash(other) + + def __ne__(self, other): + return not self.__eq__(other) + + def __repr__(self): + return "<{klass} [msghash={msghash}]>".format( + klass=self.__class__.__name__, msghash=to_hex(hash(self)) + ) + def _data_to_sign(self) -> bytes: - """ Return the binary data to be/which was signed """ + """ Return the binary data to be/which was signed + + Must be implemented by subclasses. + """ raise NotImplementedError def sign(self, signer: Signer): @@ -334,14 +345,6 @@ class Processed(SignedRetrieableMessage): message_identifier: MessageID - # TODO: move to SignedMessage - def __eq__(self, other): - return ( - isinstance(other, self.__class__) - and self._data_to_sign() == other._data_to_sign() - and self.signature == other.signature - ) - @classmethod def from_event(cls, event): return cls(message_identifier=event.message_identifier, signature=EMPTY_SIGNATURE) @@ -366,14 +369,6 @@ class ToDevice(SignedMessage): message_identifier: MessageID - # TODO: move to SignedMessage - def __eq__(self, other): - return ( - isinstance(other, self.__class__) - and self._data_to_sign() == other._data_to_sign() - and self.signature == other.signature - ) - def _data_to_sign(self) -> bytes: return pack_data( (self.cmdid, "uint8"), @@ -398,14 +393,6 @@ class Delivered(SignedMessage): delivered_message_identifier: MessageID - # TODO: move to SignedMessage - def __eq__(self, other): - return ( - isinstance(other, self.__class__) - and self._data_to_sign() == other._data_to_sign() - and self.signature == other.signature - ) - def _data_to_sign(self) -> bytes: return pack_data( (self.cmdid, "uint8"), @@ -422,14 +409,6 @@ class Pong(SignedMessage): nonce: Nonce - # TODO: move to SignedMessage - def __eq__(self, other): - return ( - isinstance(other, self.__class__) - and self._data_to_sign() == other._data_to_sign() - and self.signature == other.signature - ) - def _data_to_sign(self) -> bytes: return pack_data((self.cmdid, "uint8"), (b"\x00" * 3, "bytes"), (self.nonce, "uint64")) @@ -443,14 +422,6 @@ class Ping(SignedMessage): nonce: Nonce current_protocol_version: RaidenProtocolVersion - # TODO: move to SignedMessage - def __eq__(self, other): - return ( - isinstance(other, self.__class__) - and self._data_to_sign() == other._data_to_sign() - and self.signature == other.signature - ) - def _data_to_sign(self) -> bytes: return pack_data( (self.cmdid, "uint8"), @@ -853,14 +824,6 @@ def __post_init__(self): if self.fee > UINT256_MAX: raise ValueError("fee is too large") - # TODO: move to SignedMessage - def __eq__(self, other): - return ( - isinstance(other, self.__class__) - and self._data_to_sign() == other._data_to_sign() - and self.signature == other.signature - ) - @property def message_hash(self) -> bytes: metadata_hash = (self.metadata and self.metadata.hash) or b"" @@ -1138,15 +1101,8 @@ class RequestMonitoring(SignedMessage): def __post_init__(self): typecheck(self.balance_proof, SignedBlindedBalanceProof) - # TODO: Can this be moved to SignedMessage? We have two signatures. - # Or can we fall back to normal dataclasses comparison? - def __eq__(self, other): - return ( - isinstance(other, self.__class__) - and self._data_to_sign() == other._data_to_sign() - and self.signature == other.signature - and self.non_closing_signature == other.non_closing_signature - ) + def __hash__(self): + return hash((self._data_to_sign(), self.signature, self.non_closing_signature)) @classmethod def from_balance_proof_signed_state( @@ -1266,14 +1222,6 @@ def __post_init__(self): if self.signature is None: self.signature = EMPTY_SIGNATURE - # TODO: move to SignedMessage - def __eq__(self, other): - return ( - isinstance(other, self.__class__) - and self._data_to_sign() == other._data_to_sign() - and self.signature == other.signature - ) - @classmethod def from_channel_state(cls, channel_state: NettingChannelState) -> "PFSCapacityUpdate": # pylint: disable=unexpected-keyword-arg From 2a0e4b397067531d724996f00acc904e491c9aba Mon Sep 17 00:00:00 2001 From: Karl Bartel Date: Tue, 25 Jun 2019 16:52:54 +0200 Subject: [PATCH 17/19] Move cmdids to `messages.py` as enum Also remove the unnecessary mapping `CMDID_TO_CLASS` --- raiden/encoding/__init__.py | 0 raiden/encoding/messages.py | 13 ------ raiden/messages.py | 87 ++++++++++++++++++++----------------- 3 files changed, 47 insertions(+), 53 deletions(-) delete mode 100644 raiden/encoding/__init__.py delete mode 100644 raiden/encoding/messages.py diff --git a/raiden/encoding/__init__.py b/raiden/encoding/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/raiden/encoding/messages.py b/raiden/encoding/messages.py deleted file mode 100644 index af67443d23..0000000000 --- a/raiden/encoding/messages.py +++ /dev/null @@ -1,13 +0,0 @@ -PROCESSED = 0 -PING = 1 -PONG = 2 -SECRETREQUEST = 3 -UNLOCK = 4 -LOCKEDTRANSFER = 7 -REFUNDTRANSFER = 8 -REVEALSECRET = 11 -DELIVERED = 12 -LOCKEXPIRED = 13 -TODEVICE = 14 -WITHDRAW_REQUEST = 15 -WITHDRAW = 16 diff --git a/raiden/messages.py b/raiden/messages.py index e3447d5ffa..2614d4a919 100644 --- a/raiden/messages.py +++ b/raiden/messages.py @@ -1,3 +1,4 @@ +import enum from dataclasses import dataclass, field from datetime import datetime, timezone from hashlib import sha256 @@ -8,7 +9,6 @@ from eth_utils import to_checksum_address, to_hex from raiden.constants import EMPTY_SIGNATURE, UINT64_MAX, UINT256_MAX -from raiden.encoding import messages from raiden.exceptions import InvalidSignature from raiden.storage.serialization import DictSerializer from raiden.transfer import channel @@ -103,6 +103,23 @@ _lock_bytes_cache = LRUCache(maxsize=128) +@enum.unique +class CmdId(enum.Enum): + PROCESSED = 0 + PING = 1 + PONG = 2 + SECRETREQUEST = 3 + UNLOCK = 4 + LOCKEDTRANSFER = 7 + REFUNDTRANSFER = 8 + REVEALSECRET = 11 + DELIVERED = 12 + LOCKEXPIRED = 13 + TODEVICE = 14 + WITHDRAW_REQUEST = 15 + WITHDRAW = 16 + + def assert_envelope_values( nonce: int, channel_identifier: ChannelID, @@ -187,7 +204,7 @@ def message_from_sendevent(send_event: SendMessageEvent) -> "Message": @dataclass(repr=False, eq=False) class Message: # Needs to be set by a subclass - cmdid: ClassVar[int] + cmdid: ClassVar[CmdId] @dataclass(repr=False, eq=False) @@ -341,7 +358,7 @@ class Processed(SignedRetrieableMessage): """ # FIXME: Processed should _not_ be SignedRetrieableMessage, but only SignedMessage - cmdid: ClassVar[int] = messages.PROCESSED + cmdid: ClassVar[CmdId] = CmdId.PROCESSED message_identifier: MessageID @@ -351,7 +368,7 @@ def from_event(cls, event): def _data_to_sign(self) -> bytes: return pack_data( - (self.cmdid, "uint8"), + (self.cmdid.value, "uint8"), (b"\x00" * 3, "bytes"), # padding (self.message_identifier, "uint64"), ) @@ -365,13 +382,13 @@ class ToDevice(SignedMessage): subclass. """ - cmdid: ClassVar[int] = messages.TODEVICE + cmdid: ClassVar[CmdId] = CmdId.TODEVICE message_identifier: MessageID def _data_to_sign(self) -> bytes: return pack_data( - (self.cmdid, "uint8"), + (self.cmdid.value, "uint8"), (b"\x00" * 3, "bytes"), # padding (self.message_identifier, "uint64"), ) @@ -389,13 +406,13 @@ class Delivered(SignedMessage): successfully processed or not). """ - cmdid: ClassVar[int] = messages.DELIVERED + cmdid: ClassVar[CmdId] = CmdId.DELIVERED delivered_message_identifier: MessageID def _data_to_sign(self) -> bytes: return pack_data( - (self.cmdid, "uint8"), + (self.cmdid.value, "uint8"), (b"\x00" * 3, "bytes"), # padding (self.delivered_message_identifier, "uint64"), ) @@ -405,26 +422,28 @@ def _data_to_sign(self) -> bytes: class Pong(SignedMessage): """ Response to a Ping message. """ - cmdid: ClassVar[int] = messages.PONG + cmdid: ClassVar[CmdId] = CmdId.PONG nonce: Nonce def _data_to_sign(self) -> bytes: - return pack_data((self.cmdid, "uint8"), (b"\x00" * 3, "bytes"), (self.nonce, "uint64")) + return pack_data( + (self.cmdid.value, "uint8"), (b"\x00" * 3, "bytes"), (self.nonce, "uint64") + ) @dataclass(repr=False, eq=False) class Ping(SignedMessage): """ Healthcheck message. """ - cmdid: ClassVar[int] = messages.PING + cmdid: ClassVar[CmdId] = CmdId.PING nonce: Nonce current_protocol_version: RaidenProtocolVersion def _data_to_sign(self) -> bytes: return pack_data( - (self.cmdid, "uint8"), + (self.cmdid.value, "uint8"), (b"\x00" * 3, "bytes"), # padding (self.nonce, "uint64"), (self.current_protocol_version, "uint8"), @@ -435,7 +454,7 @@ def _data_to_sign(self) -> bytes: class SecretRequest(SignedRetrieableMessage): """ Requests the secret/preimage which unlocks a lock. """ - cmdid: ClassVar[int] = messages.SECRETREQUEST + cmdid: ClassVar[CmdId] = CmdId.SECRETREQUEST payment_identifier: PaymentID secrethash: SecretHash @@ -456,7 +475,7 @@ def from_event(cls, event): def _data_to_sign(self) -> bytes: return pack_data( - (self.cmdid, "uint8"), + (self.cmdid.value, "uint8"), (b"\x00" * 3, "bytes"), # padding (self.message_identifier, "uint64"), (self.payment_identifier, "uint64"), @@ -496,7 +515,7 @@ class Unlock(EnvelopeMessage): reject the message. """ - cmdid: ClassVar[int] = messages.UNLOCK + cmdid: ClassVar[CmdId] = CmdId.UNLOCK payment_identifier: PaymentID secret: Secret = field(repr=False) @@ -539,7 +558,7 @@ def from_event(cls, event): def message_hash(self) -> bytes: return sha3( pack_data( - (self.cmdid, "uint8"), + (self.cmdid.value, "uint8"), (b"\x00" * 3, "bytes"), # padding (self.chain_id, "uint256"), (self.message_identifier, "uint64"), @@ -562,7 +581,7 @@ class RevealSecret(SignedRetrieableMessage): This message is not sufficient to unlock a lock, refer to the Unlock. """ - cmdid: ClassVar[int] = messages.REVEALSECRET + cmdid: ClassVar[CmdId] = CmdId.REVEALSECRET secret: Secret = field(repr=False) @@ -582,7 +601,7 @@ def from_event(cls, event): def _data_to_sign(self) -> bytes: return pack_data( - (self.cmdid, "uint8"), + (self.cmdid.value, "uint8"), (b"\x00" * 3, "bytes"), # padding (self.message_identifier, "uint64"), (self.secret, "bytes32"), @@ -593,7 +612,7 @@ def _data_to_sign(self) -> bytes: class WithdrawRequest(SignedRetrieableMessage): """ Requests a signed on-chain withdraw confirmation from partner. """ - cmdid: ClassVar[int] = messages.WITHDRAW_REQUEST + cmdid: ClassVar[CmdId] = CmdId.WITHDRAW_REQUEST message_type: ClassVar[int] = MessageTypeId.WITHDRAW chain_id: ChainID @@ -632,7 +651,7 @@ def _data_to_sign(self) -> bytes: class Withdraw(SignedRetrieableMessage): """ Confirms withdraw to partner with a signature """ - cmdid: ClassVar[int] = messages.WITHDRAW + cmdid: ClassVar[CmdId] = CmdId.WITHDRAW message_type: ClassVar[int] = MessageTypeId.WITHDRAW chain_id: ChainID @@ -805,7 +824,7 @@ class LockedTransfer(LockedTransferBase): not know which nodes are available, thus an estimated value is used. """ - cmdid: ClassVar[int] = messages.LOCKEDTRANSFER + cmdid: ClassVar[CmdId] = CmdId.LOCKEDTRANSFER target: TargetAddress initiator: InitiatorAddress @@ -829,7 +848,7 @@ def message_hash(self) -> bytes: metadata_hash = (self.metadata and self.metadata.hash) or b"" return sha3( pack_data( - (self.cmdid, "uint8"), + (self.cmdid.value, "uint8"), (b"\x00" * 3, "bytes"), # padding (self.nonce, "uint64"), (self.chain_id, "uint256"), @@ -897,7 +916,7 @@ class RefundTransfer(LockedTransfer): to complete the transfer. """ - cmdid: ClassVar[int] = messages.REFUNDTRANSFER + cmdid: ClassVar[CmdId] = CmdId.REFUNDTRANSFER @classmethod def from_event(cls, event): @@ -939,7 +958,7 @@ def message_hash(self) -> bytes: # Refactor this into something shared. return sha3( pack_data( - (self.cmdid, "uint8"), + (self.cmdid.value, "uint8"), (b"\x00" * 3, "bytes"), # padding (self.nonce, "uint64"), (self.chain_id, "uint256"), @@ -980,7 +999,7 @@ class LockExpired(EnvelopeMessage): in-flight, vide Unlock for examples. """ - cmdid: ClassVar[int] = messages.LOCKEXPIRED + cmdid: ClassVar[CmdId] = CmdId.LOCKEXPIRED recipient: Address secrethash: SecretHash @@ -1008,7 +1027,7 @@ def from_event(cls, event): def message_hash(self) -> bytes: return sha3( pack_data( - (self.cmdid, "uint8"), + (self.cmdid.value, "uint8"), (b"\x00" * 3, "bytes"), # padding (self.nonce, "uint64"), (self.chain_id, "uint256"), @@ -1311,19 +1330,7 @@ def lockedtransfersigned_from_message(message: LockedTransfer) -> LockedTransfer return transfer_state -CMDID_TO_CLASS: Dict[int, Type[Message]] = { - messages.DELIVERED: Delivered, - messages.LOCKEDTRANSFER: LockedTransfer, - messages.PING: Ping, - messages.PONG: Pong, - messages.PROCESSED: Processed, - messages.REFUNDTRANSFER: RefundTransfer, - messages.REVEALSECRET: RevealSecret, - messages.UNLOCK: Unlock, - messages.SECRETREQUEST: SecretRequest, - messages.LOCKEXPIRED: LockExpired, - messages.TODEVICE: ToDevice, +CLASSNAME_TO_CLASS: Dict[str, Type[Message]] = { + klass.__name__: klass for klass in Message.__subclasses__() } - -CLASSNAME_TO_CLASS = {klass.__name__: klass for klass in CMDID_TO_CLASS.values()} CLASSNAME_TO_CLASS["Secret"] = Unlock From e9f5ff1bf941b4ff04dcc8e7a13ebb8738dfc837 Mon Sep 17 00:00:00 2001 From: Karl Bartel Date: Tue, 25 Jun 2019 18:08:36 +0200 Subject: [PATCH 18/19] Reduce duplication between RefundTr. and LockedTr. --- raiden/messages.py | 230 ++++++++++++++++----------------------------- 1 file changed, 79 insertions(+), 151 deletions(-) diff --git a/raiden/messages.py b/raiden/messages.py index 2614d4a919..0957de6984 100644 --- a/raiden/messages.py +++ b/raiden/messages.py @@ -3,6 +3,7 @@ from datetime import datetime, timezone from hashlib import sha256 from operator import attrgetter +from typing import overload import rlp from cachetools import LRUCache, cached @@ -74,7 +75,6 @@ "EnvelopeMessage", "Lock", "LockedTransfer", - "LockedTransferBase", "LockExpired", "Message", "Metadata", @@ -172,34 +172,32 @@ def assert_transfer_values(payment_identifier, token, recipient): def message_from_sendevent(send_event: SendMessageEvent) -> "Message": if type(send_event) == SendLockedTransfer: assert isinstance(send_event, SendLockedTransfer), MYPY_ANNOTATION - message = LockedTransfer.from_event(send_event) + return LockedTransfer.from_event(send_event) elif type(send_event) == SendSecretReveal: assert isinstance(send_event, SendSecretReveal), MYPY_ANNOTATION - message = RevealSecret.from_event(send_event) + return RevealSecret.from_event(send_event) elif type(send_event) == SendBalanceProof: assert isinstance(send_event, SendBalanceProof), MYPY_ANNOTATION - message = Unlock.from_event(send_event) + return Unlock.from_event(send_event) elif type(send_event) == SendSecretRequest: assert isinstance(send_event, SendSecretRequest), MYPY_ANNOTATION - message = SecretRequest.from_event(send_event) + return SecretRequest.from_event(send_event) elif type(send_event) == SendRefundTransfer: assert isinstance(send_event, SendRefundTransfer), MYPY_ANNOTATION - message = RefundTransfer.from_event(send_event) + return RefundTransfer.from_event(send_event) elif type(send_event) == SendLockExpired: assert isinstance(send_event, SendLockExpired), MYPY_ANNOTATION - message = LockExpired.from_event(send_event) + return LockExpired.from_event(send_event) elif type(send_event) == SendWithdrawRequest: - message = WithdrawRequest.from_event(send_event) + return WithdrawRequest.from_event(send_event) elif type(send_event) == SendWithdraw: - message = Withdraw.from_event(send_event) + return Withdraw.from_event(send_event) elif type(send_event) == SendProcessed: assert isinstance(send_event, SendProcessed), MYPY_ANNOTATION - message = Processed.from_event(send_event) + return Processed.from_event(send_event) else: raise ValueError(f"Unknown event type {send_event}") - return message - @dataclass(repr=False, eq=False) class Message: @@ -776,56 +774,6 @@ class LockedTransferBase(EnvelopeMessage): token: TokenAddress recipient: Address lock: Lock - - def __post_init__(self): - super().__post_init__() - assert_transfer_values(self.payment_identifier, self.token, self.recipient) - - def pack(self, packed) -> None: - packed.chain_id = self.chain_id - packed.message_identifier = self.message_identifier - packed.payment_identifier = self.payment_identifier - packed.nonce = self.nonce - packed.token_network_address = self.token_network_address - packed.token = self.token - packed.channel_identifier = self.channel_identifier - packed.transferred_amount = self.transferred_amount - packed.locked_amount = self.locked_amount - packed.recipient = self.recipient - packed.locksroot = self.locksroot - - lock = self.lock - packed.amount = lock.amount - packed.expiration = lock.expiration - packed.secrethash = lock.secrethash - - packed.signature = self.signature - - -@dataclass(repr=False, eq=False) -class LockedTransfer(LockedTransferBase): - """ Message used to reserve tokens for a new mediated transfer. - - For this message to be valid, the sender must: - - - Use a lock.amount smaller then its current capacity. If the amount is - higher, then the recipient will reject it, as it means spending money it - does not own. - - Have the new lock represented in locksroot. - - Increase the locked_amount by exactly `lock.amount` otherwise the message - would be rejected by the recipient. If the locked_amount is increased by - more, then funds may get locked in the channel. If the locked_amount is - increased by less, then the recipient will reject the message as it may - mean it received the funds with an on-chain unlock. - - The initiator will estimate the fees based on the available routes and - incorporate it in the lock's amount. Note that with permissive routing it - is not possible to predetermine the exact fee amount, as the initiator does - not know which nodes are available, thus an estimated value is used. - """ - - cmdid: ClassVar[CmdId] = CmdId.LOCKEDTRANSFER - target: TargetAddress initiator: InitiatorAddress fee: int @@ -833,6 +781,7 @@ class LockedTransfer(LockedTransferBase): def __post_init__(self): super().__post_init__() + assert_transfer_values(self.payment_identifier, self.token, self.recipient) if len(self.target) != 20: raise ValueError("target is an invalid address") @@ -843,36 +792,20 @@ def __post_init__(self): if self.fee > UINT256_MAX: raise ValueError("fee is too large") - @property - def message_hash(self) -> bytes: - metadata_hash = (self.metadata and self.metadata.hash) or b"" - return sha3( - pack_data( - (self.cmdid.value, "uint8"), - (b"\x00" * 3, "bytes"), # padding - (self.nonce, "uint64"), - (self.chain_id, "uint256"), - (self.message_identifier, "uint64"), - (self.payment_identifier, "uint64"), - (self.lock.expiration, "uint256"), - (self.token_network_address, "address"), - (self.token, "address"), - (self.channel_identifier, "uint256"), - (self.recipient, "address"), - (self.target, "address"), - (self.initiator, "address"), - (self.locksroot, "bytes32"), - (self.lock.secrethash, "bytes32"), - (self.transferred_amount, "uint256"), - (self.locked_amount, "uint256"), - (self.lock.amount, "uint256"), - (self.fee, "uint256"), - ) - + metadata_hash - ) - + @overload @classmethod def from_event(cls, event: SendLockedTransfer) -> "LockedTransfer": + # pylint: disable=unused-argument + ... + + @overload # noqa: F811 + @classmethod + def from_event(cls, event: SendRefundTransfer) -> "RefundTransfer": + # pylint: disable=unused-argument + ... + + @classmethod # noqa: F811 + def from_event(cls, event): transfer = event.transfer balance_proof = transfer.balance_proof lock = Lock( @@ -905,9 +838,62 @@ def from_event(cls, event: SendLockedTransfer) -> "LockedTransfer": ), ) + def _packed_data(self): + return pack_data( + (self.cmdid.value, "uint8"), + (b"\x00" * 3, "bytes"), # padding + (self.nonce, "uint64"), + (self.chain_id, "uint256"), + (self.message_identifier, "uint64"), + (self.payment_identifier, "uint64"), + (self.lock.expiration, "uint256"), + (self.token_network_address, "address"), + (self.token, "address"), + (self.channel_identifier, "uint256"), + (self.recipient, "address"), + (self.target, "address"), + (self.initiator, "address"), + (self.locksroot, "bytes32"), + (self.lock.secrethash, "bytes32"), + (self.transferred_amount, "uint256"), + (self.locked_amount, "uint256"), + (self.lock.amount, "uint256"), + (self.fee, "uint256"), + ) + @dataclass(repr=False, eq=False) -class RefundTransfer(LockedTransfer): +class LockedTransfer(LockedTransferBase): + """ Message used to reserve tokens for a new mediated transfer. + + For this message to be valid, the sender must: + + - Use a lock.amount smaller then its current capacity. If the amount is + higher, then the recipient will reject it, as it means spending money it + does not own. + - Have the new lock represented in locksroot. + - Increase the locked_amount by exactly `lock.amount` otherwise the message + would be rejected by the recipient. If the locked_amount is increased by + more, then funds may get locked in the channel. If the locked_amount is + increased by less, then the recipient will reject the message as it may + mean it received the funds with an on-chain unlock. + + The initiator will estimate the fees based on the available routes and + incorporate it in the lock's amount. Note that with permissive routing it + is not possible to predetermine the exact fee amount, as the initiator does + not know which nodes are available, thus an estimated value is used. + """ + + cmdid: ClassVar[CmdId] = CmdId.LOCKEDTRANSFER + + @property + def message_hash(self) -> bytes: + metadata_hash = (self.metadata and self.metadata.hash) or b"" + return sha3(self._packed_data() + metadata_hash) + + +@dataclass(repr=False, eq=False) +class RefundTransfer(LockedTransferBase): """ A message used when a payee does not have any available routes to forward the transfer. @@ -918,67 +904,9 @@ class RefundTransfer(LockedTransfer): cmdid: ClassVar[CmdId] = CmdId.REFUNDTRANSFER - @classmethod - def from_event(cls, event): - transfer = event.transfer - balance_proof = transfer.balance_proof - lock = Lock( - amount=transfer.lock.amount, - expiration=transfer.lock.expiration, - secrethash=transfer.lock.secrethash, - ) - fee = 0 - - # pylint: disable=unexpected-keyword-arg - return cls( - chain_id=balance_proof.chain_id, - message_identifier=event.message_identifier, - payment_identifier=transfer.payment_identifier, - nonce=balance_proof.nonce, - token_network_address=balance_proof.token_network_address, - token=transfer.token, - channel_identifier=balance_proof.channel_identifier, - transferred_amount=balance_proof.transferred_amount, - locked_amount=balance_proof.locked_amount, - recipient=event.recipient, - locksroot=balance_proof.locksroot, - lock=lock, - target=transfer.target, - initiator=transfer.initiator, - fee=fee, - signature=EMPTY_SIGNATURE, - metadata=Metadata( - routes=[RouteMetadata(route=r.route) for r in transfer.route_states] - ), - ) - @property def message_hash(self) -> bytes: - # TODO: This is the same as for LockedTransfer except for the metadata. - # Refactor this into something shared. - return sha3( - pack_data( - (self.cmdid.value, "uint8"), - (b"\x00" * 3, "bytes"), # padding - (self.nonce, "uint64"), - (self.chain_id, "uint256"), - (self.message_identifier, "uint64"), - (self.payment_identifier, "uint64"), - (self.lock.expiration, "uint256"), - (self.token_network_address, "address"), - (self.token, "address"), - (self.channel_identifier, "uint256"), - (self.recipient, "address"), - (self.target, "address"), - (self.initiator, "address"), - (self.locksroot, "bytes32"), - (self.lock.secrethash, "bytes32"), - (self.transferred_amount, "uint256"), - (self.locked_amount, "uint256"), - (self.lock.amount, "uint256"), - (self.fee, "uint256"), - ) - ) + return sha3(self._packed_data()) @dataclass(repr=False, eq=False) @@ -1311,7 +1239,7 @@ def from_channel_state(cls, channel_state: NettingChannelState): ) -def lockedtransfersigned_from_message(message: LockedTransfer) -> LockedTransferSignedState: +def lockedtransfersigned_from_message(message: LockedTransferBase) -> LockedTransferSignedState: """ Create LockedTransferSignedState from a LockedTransfer message. """ balance_proof = balanceproof_from_envelope(message) From 0ea708d5190f2c30b6a83bbddfa4277788934a16 Mon Sep 17 00:00:00 2001 From: Karl Bartel Date: Thu, 4 Jul 2019 17:50:28 +0200 Subject: [PATCH 19/19] Remove signature checks from `_data_to_sign` The check for `reward_proof_signature` does make any sense, because we're just gathering the data to create that signature in that function. I've removed the check for `non_closing_signature` along with it because having it in this place seemed inconsistent (we have no such checks in `_data_to_sign` for any message) and I don't think this is a good place for such checks. Before this PR, the check was applied on serialization. I don't see a place where I could hook into to reproduce that behaviour and it probably makes more sense to move towards signed-by-design messages, anyway. --- raiden/messages.py | 5 ----- raiden/tests/unit/test_messages.py | 7 ++++--- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/raiden/messages.py b/raiden/messages.py index 0957de6984..d1b6625e55 100644 --- a/raiden/messages.py +++ b/raiden/messages.py @@ -1078,11 +1078,6 @@ def reward_proof_signature(self) -> Optional[Signature]: def _data_to_sign(self) -> bytes: """ Return the binary data to be/which was signed """ - if self.non_closing_signature is None: - raise ValueError("non_closing_signature missing, did you forget to sign()?") - if self.reward_proof_signature is None: - raise ValueError("reward_proof_signature missing, did you forget to sign()?") - packed = pack_reward_proof( canonical_identifier=CanonicalIdentifier( chain_identifier=self.balance_proof.chain_id, diff --git a/raiden/tests/unit/test_messages.py b/raiden/tests/unit/test_messages.py index 5d39ff3fcb..f068b3c67f 100644 --- a/raiden/tests/unit/test_messages.py +++ b/raiden/tests/unit/test_messages.py @@ -60,9 +60,10 @@ def test_request_monitoring() -> None: direct_created = RequestMonitoring.from_balance_proof_signed_state( balance_proof, reward_amount=55, monitoring_service_contract_address=MSC_ADDRESS ) - with pytest.raises(ValueError): - # equality test uses `validated` packed format - assert direct_created == request_monitoring + # `direct_created` is not signed while request_monitoring is + assert DictSerializer().serialize(direct_created) != DictSerializer().serialize( + request_monitoring + ) direct_created.sign(signer) # Instances created from same balance proof are equal