From d2c6333f87f7c0f12a91ffa167d1fe72bcbf2007 Mon Sep 17 00:00:00 2001 From: Alexandru Popenta Date: Thu, 28 Mar 2024 17:44:28 +0200 Subject: [PATCH] apply options for hash signing --- multiversx_sdk/core/constants.py | 2 + multiversx_sdk/core/transaction_computer.py | 21 +++++++-- multiversx_sdk/core/transaction_test.py | 49 ++++++++++++++++++++- 3 files changed, 67 insertions(+), 5 deletions(-) diff --git a/multiversx_sdk/core/constants.py b/multiversx_sdk/core/constants.py index 61e8a54c..80a5e8dd 100644 --- a/multiversx_sdk/core/constants.py +++ b/multiversx_sdk/core/constants.py @@ -23,3 +23,5 @@ DEFAULT_MESSAGE_VERSION = 1 BECH32_ADDRESS_LENGTH = 62 + +MIN_TRANSACTION_VERSION_THAT_SUPPORTS_OPTIONS = 2 diff --git a/multiversx_sdk/core/transaction_computer.py b/multiversx_sdk/core/transaction_computer.py index 4842d9ce..1a9d6a2a 100644 --- a/multiversx_sdk/core/transaction_computer.py +++ b/multiversx_sdk/core/transaction_computer.py @@ -6,9 +6,10 @@ from Cryptodome.Hash import keccak -from multiversx_sdk.core.constants import (BECH32_ADDRESS_LENGTH, DIGEST_SIZE, - TRANSACTION_OPTIONS_TX_GUARDED, - TRANSACTION_OPTIONS_TX_HASH_SIGN) +from multiversx_sdk.core.constants import ( + BECH32_ADDRESS_LENGTH, DIGEST_SIZE, + MIN_TRANSACTION_VERSION_THAT_SUPPORTS_OPTIONS, + TRANSACTION_OPTIONS_TX_GUARDED, TRANSACTION_OPTIONS_TX_HASH_SIGN) from multiversx_sdk.core.errors import BadUsageError, NotEnoughGasError from multiversx_sdk.core.interfaces import INetworkConfig, ITransaction from multiversx_sdk.core.proto.transaction_serializer import ProtoSerializer @@ -61,10 +62,18 @@ def has_options_set_for_hash_signing(self, transaction: ITransaction) -> bool: return (transaction.options & TRANSACTION_OPTIONS_TX_HASH_SIGN) == TRANSACTION_OPTIONS_TX_HASH_SIGN def apply_guardian(self, transaction: ITransaction, guardian: str) -> None: - transaction.version = 2 + if transaction.version < MIN_TRANSACTION_VERSION_THAT_SUPPORTS_OPTIONS: + transaction.version = MIN_TRANSACTION_VERSION_THAT_SUPPORTS_OPTIONS + transaction.options = transaction.options | TRANSACTION_OPTIONS_TX_GUARDED transaction.guardian = guardian + def apply_options_for_hash_signing(self, transaction: ITransaction) -> None: + if transaction.version < MIN_TRANSACTION_VERSION_THAT_SUPPORTS_OPTIONS: + transaction.version = MIN_TRANSACTION_VERSION_THAT_SUPPORTS_OPTIONS + + transaction.options = transaction.options | TRANSACTION_OPTIONS_TX_HASH_SIGN + def _ensure_fields(self, transaction: ITransaction) -> None: if len(transaction.sender) != BECH32_ADDRESS_LENGTH: raise BadUsageError("Invalid `sender` field. Should be the bech32 address of the sender.") @@ -75,6 +84,10 @@ def _ensure_fields(self, transaction: ITransaction) -> None: if not len(transaction.chain_id): raise BadUsageError("The `chainID` field is not set") + if transaction.version < MIN_TRANSACTION_VERSION_THAT_SUPPORTS_OPTIONS: + if self.has_options_set_for_guarded_transaction(transaction) or self.has_options_set_for_hash_signing(transaction): + raise BadUsageError(f"Non-empty transaction options requires transaction version >= {MIN_TRANSACTION_VERSION_THAT_SUPPORTS_OPTIONS}") + def _to_dictionary(self, transaction: ITransaction) -> Dict[str, Any]: dictionary: Dict[str, Any] = OrderedDict() dictionary["nonce"] = transaction.nonce diff --git a/multiversx_sdk/core/transaction_test.py b/multiversx_sdk/core/transaction_test.py index 72f6a750..61aea47e 100644 --- a/multiversx_sdk/core/transaction_test.py +++ b/multiversx_sdk/core/transaction_test.py @@ -2,7 +2,9 @@ import pytest -from multiversx_sdk.core.errors import NotEnoughGasError +from multiversx_sdk.core.constants import \ + MIN_TRANSACTION_VERSION_THAT_SUPPORTS_OPTIONS +from multiversx_sdk.core.errors import BadUsageError, NotEnoughGasError from multiversx_sdk.core.proto.transaction_serializer import ProtoSerializer from multiversx_sdk.core.transaction import Transaction from multiversx_sdk.core.transaction_computer import TransactionComputer @@ -22,6 +24,7 @@ def __init__(self, min_gas_limit: int = 50000) -> None: class TestTransaction: wallets = load_wallets() alice = wallets["alice"] + bob = wallets["bob"] carol = wallets["carol"] transaction_computer = TransactionComputer() @@ -245,3 +248,47 @@ def test_sign_transaction_by_hash(self): tx.signature = pem.secret_key.sign(serialized) assert tx.signature.hex() == "f0c81f2393b1ec5972c813f817bae8daa00ade91c6f75ea604ab6a4d2797aca4378d783023ff98f1a02717fe4f24240cdfba0b674ee9abb18042203d713bc70a" + + def test_apply_guardian_with_hash_signing(self): + tx = Transaction( + sender="erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", + receiver="erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx", + value=0, + gas_limit=50000, + version=1, + chain_id="localnet", + nonce=89 + ) + + self.transaction_computer.apply_options_for_hash_signing(tx) + assert tx.version == 2 + assert tx.options == 1 + + self.transaction_computer.apply_guardian(transaction=tx, guardian=self.carol.label) + assert tx.version == 2 + assert tx.options == 3 + + def test_ensure_transaction_is_valid(self): + tx = Transaction( + sender="invalid_sender", + receiver=self.bob.label, + gas_limit=50000, + chain_id="" + ) + + with pytest.raises(BadUsageError, match="Invalid `sender` field. Should be the bech32 address of the sender."): + self.transaction_computer.compute_bytes_for_signing(tx) + + tx.sender = self.alice.label + with pytest.raises(BadUsageError, match="The `chainID` field is not set"): + self.transaction_computer.compute_bytes_for_signing(tx) + + tx.chain_id = "localnet" + tx.version = 1 + tx.options = 2 + with pytest.raises(BadUsageError, match=f"Non-empty transaction options requires transaction version >= {MIN_TRANSACTION_VERSION_THAT_SUPPORTS_OPTIONS}"): + self.transaction_computer.compute_bytes_for_signing(tx) + + self.transaction_computer.apply_options_for_hash_signing(tx) + assert tx.version == 2 + assert tx.options == 3