From dbd97437fa293fd65d56239da865c7dadea8cd11 Mon Sep 17 00:00:00 2001 From: Mohit Yadav Date: Fri, 1 May 2026 19:17:56 +0530 Subject: [PATCH 1/7] fix: prevent duplicate signatures in _signature_map Signed-off-by: Mohit Yadav --- .../transaction/transaction.py | 8 ++- tests/unit/test_signature_deduplication.py | 53 +++++++++++++++++++ 2 files changed, 59 insertions(+), 2 deletions(-) create mode 100644 tests/unit/test_signature_deduplication.py diff --git a/src/hiero_sdk_python/transaction/transaction.py b/src/hiero_sdk_python/transaction/transaction.py index 692877c20..763aac655 100644 --- a/src/hiero_sdk_python/transaction/transaction.py +++ b/src/hiero_sdk_python/transaction/transaction.py @@ -192,8 +192,12 @@ def sign(self, private_key: PrivateKey) -> Transaction: # We initialize the signature map for this body_bytes if it doesn't exist yet self._signature_map.setdefault(body_bytes, basic_types_pb2.SignatureMap()) - # Append the signature pair to the signature map for this transaction body - self._signature_map[body_bytes].sigPair.append(sig_pair) + # deduplication check + already_signed = any(sp.pubKeyPrefix == public_key_bytes for sp in self._signature_map[body_bytes].sigPair) + + # append only if not already signed + if not already_signed: + self._signature_map[body_bytes].sigPair.append(sig_pair) return self diff --git a/tests/unit/test_signature_deduplication.py b/tests/unit/test_signature_deduplication.py new file mode 100644 index 000000000..fdbd8cb4c --- /dev/null +++ b/tests/unit/test_signature_deduplication.py @@ -0,0 +1,53 @@ +from __future__ import annotations + +from hiero_sdk_python.account.account_id import AccountId +from hiero_sdk_python.crypto.private_key import PrivateKey +from hiero_sdk_python.tokens.token_mint_transaction import TokenMintTransaction +from hiero_sdk_python.transaction.transaction_id import TransactionId + + +def test_duplicate_signature_not_added(): + tx = TokenMintTransaction() + + tx.set_transaction_id(TransactionId.generate(AccountId(0, 0, 1234))) + tx.set_node_account_id(AccountId(0, 0, 3)) + + key = PrivateKey.generate_ed25519() + + tx.freeze() + + tx.sign(key) + tx.sign(key) + + body_bytes = next(iter(tx._signature_map.keys())) + sig_pairs = tx._signature_map[body_bytes].sigPair + + assert len(sig_pairs) == 1, "Expected 1 signature for duplicate key" + + +def test_multiple_keys_still_work(): + tx = TokenMintTransaction() + + tx.set_transaction_id(TransactionId.generate(AccountId(0, 0, 1234))) + tx.set_node_account_id(AccountId(0, 0, 3)) + + key1 = PrivateKey.generate_ed25519() + key2 = PrivateKey.generate_ed25519() + + tx.freeze() + + tx.sign(key1) + tx.sign(key2) + + body_bytes = next(iter(tx._signature_map.keys())) + sig_pairs = tx._signature_map[body_bytes].sigPair + + assert len(sig_pairs) == 2, "Expected 2 signatures for different keys" + + pubkey_prefixes = {sp.pubKeyPrefix for sp in sig_pairs} + expected_prefixes = { + key1.public_key().to_bytes(), + key2.public_key().to_bytes(), + } + + assert pubkey_prefixes == expected_prefixes, "Signatures should match key1 and key2 exactly" From 6b1bc8c75802eeecd3ef4f8c75ea2796a723a35f Mon Sep 17 00:00:00 2001 From: Mohit Yadav Date: Fri, 1 May 2026 19:17:56 +0530 Subject: [PATCH 2/7] fix: prevent duplicate signatures in _signature_map Signed-off-by: Mohit Yadav --- tests/unit/signature_deduplication_test.py | 44 ++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 tests/unit/signature_deduplication_test.py diff --git a/tests/unit/signature_deduplication_test.py b/tests/unit/signature_deduplication_test.py new file mode 100644 index 000000000..4a1d534ab --- /dev/null +++ b/tests/unit/signature_deduplication_test.py @@ -0,0 +1,44 @@ +# RENAME FILE TO: tests/unit/signature_deduplication_test.py + +from __future__ import annotations + +from hiero_sdk_python.account.account_id import AccountId +from hiero_sdk_python.crypto.private_key import PrivateKey +from hiero_sdk_python.tokens.token_id import TokenId +from hiero_sdk_python.tokens.token_mint_transaction import TokenMintTransaction +from hiero_sdk_python.transaction.transaction_id import TransactionId + + +def test_duplicate_signature_not_added(): + tx = TokenMintTransaction() + tx.set_transaction_id(TransactionId.generate(AccountId(0, 0, 1234))) + tx.set_node_account_id(AccountId(0, 0, 3)) + tx.set_token_id(TokenId(0, 0, 1)) + key = PrivateKey.generate_ed25519() + tx.freeze() + tx.sign(key) + tx.sign(key) + body_bytes = next(iter(tx._signature_map.keys())) + sig_pairs = tx._signature_map[body_bytes].sigPair + assert len(sig_pairs) == 1, "Expected 1 signature for duplicate key" + + +def test_multiple_keys_still_work(): + tx = TokenMintTransaction() + tx.set_transaction_id(TransactionId.generate(AccountId(0, 0, 1234))) + tx.set_node_account_id(AccountId(0, 0, 3)) + tx.set_token_id(TokenId(0, 0, 1)) + key1 = PrivateKey.generate_ed25519() + key2 = PrivateKey.generate_ed25519() + tx.freeze() + tx.sign(key1) + tx.sign(key2) + body_bytes = next(iter(tx._signature_map.keys())) + sig_pairs = tx._signature_map[body_bytes].sigPair + assert len(sig_pairs) == 2, "Expected 2 signatures for different keys" + pubkey_prefixes = {sp.pubKeyPrefix for sp in sig_pairs} + expected_prefixes = { + key1.public_key().to_bytes(), + key2.public_key().to_bytes(), + } + assert pubkey_prefixes == expected_prefixes, "Signatures should match key1 and key2 exactly" From 03023fdfbb9f8cea8f03dcbf8e0ef2afd19ef38a Mon Sep 17 00:00:00 2001 From: Mohit Yadav Date: Sat, 2 May 2026 17:34:23 +0530 Subject: [PATCH 3/7] fix: add amount to TokenMintTransaction in deduplication tests Signed-off-by: Mohit Yadav --- tests/unit/signature_deduplication_test.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/unit/signature_deduplication_test.py b/tests/unit/signature_deduplication_test.py index 4a1d534ab..3e61391c2 100644 --- a/tests/unit/signature_deduplication_test.py +++ b/tests/unit/signature_deduplication_test.py @@ -1,5 +1,3 @@ -# RENAME FILE TO: tests/unit/signature_deduplication_test.py - from __future__ import annotations from hiero_sdk_python.account.account_id import AccountId @@ -14,6 +12,7 @@ def test_duplicate_signature_not_added(): tx.set_transaction_id(TransactionId.generate(AccountId(0, 0, 1234))) tx.set_node_account_id(AccountId(0, 0, 3)) tx.set_token_id(TokenId(0, 0, 1)) + tx.set_amount(100) key = PrivateKey.generate_ed25519() tx.freeze() tx.sign(key) @@ -28,6 +27,7 @@ def test_multiple_keys_still_work(): tx.set_transaction_id(TransactionId.generate(AccountId(0, 0, 1234))) tx.set_node_account_id(AccountId(0, 0, 3)) tx.set_token_id(TokenId(0, 0, 1)) + tx.set_amount(100) key1 = PrivateKey.generate_ed25519() key2 = PrivateKey.generate_ed25519() tx.freeze() @@ -38,7 +38,7 @@ def test_multiple_keys_still_work(): assert len(sig_pairs) == 2, "Expected 2 signatures for different keys" pubkey_prefixes = {sp.pubKeyPrefix for sp in sig_pairs} expected_prefixes = { - key1.public_key().to_bytes(), - key2.public_key().to_bytes(), + key1.public_key().to_bytes_raw(), + key2.public_key().to_bytes_raw(), } assert pubkey_prefixes == expected_prefixes, "Signatures should match key1 and key2 exactly" From e368d7c9f226a615767c11c3ad89d2c0c1640e35 Mon Sep 17 00:00:00 2001 From: Mohit Yadav Date: Sat, 2 May 2026 17:42:18 +0530 Subject: [PATCH 4/7] fix: guard against empty signature_map in deduplication tests Signed-off-by: Mohit Yadav --- tests/unit/signature_deduplication_test.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/unit/signature_deduplication_test.py b/tests/unit/signature_deduplication_test.py index 3e61391c2..7d3ad9af5 100644 --- a/tests/unit/signature_deduplication_test.py +++ b/tests/unit/signature_deduplication_test.py @@ -17,6 +17,7 @@ def test_duplicate_signature_not_added(): tx.freeze() tx.sign(key) tx.sign(key) + assert tx._signature_map, "signature_map should not be empty after freeze+sign" # ← ADD HERE body_bytes = next(iter(tx._signature_map.keys())) sig_pairs = tx._signature_map[body_bytes].sigPair assert len(sig_pairs) == 1, "Expected 1 signature for duplicate key" @@ -33,6 +34,7 @@ def test_multiple_keys_still_work(): tx.freeze() tx.sign(key1) tx.sign(key2) + assert tx._signature_map, "signature_map should not be empty after freeze+sign" body_bytes = next(iter(tx._signature_map.keys())) sig_pairs = tx._signature_map[body_bytes].sigPair assert len(sig_pairs) == 2, "Expected 2 signatures for different keys" From a89df7e26aebfac6ea25e7766be252a7b352c62a Mon Sep 17 00:00:00 2001 From: Mohit Yadav Date: Sat, 2 May 2026 17:43:52 +0530 Subject: [PATCH 5/7] Delete tests/unit/test_signature_deduplication.py Signed-off-by: Mohit Yadav --- tests/unit/test_signature_deduplication.py | 53 ---------------------- 1 file changed, 53 deletions(-) delete mode 100644 tests/unit/test_signature_deduplication.py diff --git a/tests/unit/test_signature_deduplication.py b/tests/unit/test_signature_deduplication.py deleted file mode 100644 index fdbd8cb4c..000000000 --- a/tests/unit/test_signature_deduplication.py +++ /dev/null @@ -1,53 +0,0 @@ -from __future__ import annotations - -from hiero_sdk_python.account.account_id import AccountId -from hiero_sdk_python.crypto.private_key import PrivateKey -from hiero_sdk_python.tokens.token_mint_transaction import TokenMintTransaction -from hiero_sdk_python.transaction.transaction_id import TransactionId - - -def test_duplicate_signature_not_added(): - tx = TokenMintTransaction() - - tx.set_transaction_id(TransactionId.generate(AccountId(0, 0, 1234))) - tx.set_node_account_id(AccountId(0, 0, 3)) - - key = PrivateKey.generate_ed25519() - - tx.freeze() - - tx.sign(key) - tx.sign(key) - - body_bytes = next(iter(tx._signature_map.keys())) - sig_pairs = tx._signature_map[body_bytes].sigPair - - assert len(sig_pairs) == 1, "Expected 1 signature for duplicate key" - - -def test_multiple_keys_still_work(): - tx = TokenMintTransaction() - - tx.set_transaction_id(TransactionId.generate(AccountId(0, 0, 1234))) - tx.set_node_account_id(AccountId(0, 0, 3)) - - key1 = PrivateKey.generate_ed25519() - key2 = PrivateKey.generate_ed25519() - - tx.freeze() - - tx.sign(key1) - tx.sign(key2) - - body_bytes = next(iter(tx._signature_map.keys())) - sig_pairs = tx._signature_map[body_bytes].sigPair - - assert len(sig_pairs) == 2, "Expected 2 signatures for different keys" - - pubkey_prefixes = {sp.pubKeyPrefix for sp in sig_pairs} - expected_prefixes = { - key1.public_key().to_bytes(), - key2.public_key().to_bytes(), - } - - assert pubkey_prefixes == expected_prefixes, "Signatures should match key1 and key2 exactly" From 5d060cf63bdb9cb6791dbf3ea79c91e38e7fd67c Mon Sep 17 00:00:00 2001 From: Mohit Yadav Date: Sat, 2 May 2026 17:42:18 +0530 Subject: [PATCH 6/7] fix: guard against empty signature_map in deduplication tests Signed-off-by: Mohit Yadav --- tests/unit/transaction_test.py | 43 ++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/tests/unit/transaction_test.py b/tests/unit/transaction_test.py index efe8cbe84..5821d9e7a 100644 --- a/tests/unit/transaction_test.py +++ b/tests/unit/transaction_test.py @@ -3,6 +3,7 @@ import pytest from hiero_sdk_python.account.account_create_transaction import AccountCreateTransaction +from hiero_sdk_python.account.account_id import AccountId from hiero_sdk_python.crypto.private_key import PrivateKey from hiero_sdk_python.exceptions import ReceiptStatusError from hiero_sdk_python.hapi.services import ( @@ -14,6 +15,9 @@ transaction_response_pb2, ) from hiero_sdk_python.response_code import ResponseCode +from hiero_sdk_python.tokens.token_id import TokenId +from hiero_sdk_python.tokens.token_mint_transaction import TokenMintTransaction +from hiero_sdk_python.transaction.transaction_id import TransactionId from hiero_sdk_python.transaction.transaction_receipt import TransactionReceipt from hiero_sdk_python.transaction.transaction_response import TransactionResponse from tests.unit.mock_server import mock_hedera_servers @@ -117,3 +121,42 @@ def test_execute_returns_receipt_without_error_when_validation_disabled(): receipt = tx.execute(client) assert receipt.status == ResponseCode.INVALID_SIGNATURE + + +def test_duplicate_signature_not_added(): + tx = TokenMintTransaction() + tx.set_transaction_id(TransactionId.generate(AccountId(0, 0, 1234))) + tx.set_node_account_id(AccountId(0, 0, 3)) + tx.set_token_id(TokenId(0, 0, 1)) + tx.set_amount(100) + key = PrivateKey.generate_ed25519() + tx.freeze() + tx.sign(key) + tx.sign(key) + assert tx._signature_map, "signature_map should not be empty after freeze+sign" # ← ADD HERE + body_bytes = next(iter(tx._signature_map.keys())) + sig_pairs = tx._signature_map[body_bytes].sigPair + assert len(sig_pairs) == 1, "Expected 1 signature for duplicate key" + + +def test_multiple_keys_still_work(): + tx = TokenMintTransaction() + tx.set_transaction_id(TransactionId.generate(AccountId(0, 0, 1234))) + tx.set_node_account_id(AccountId(0, 0, 3)) + tx.set_token_id(TokenId(0, 0, 1)) + tx.set_amount(100) + key1 = PrivateKey.generate_ed25519() + key2 = PrivateKey.generate_ed25519() + tx.freeze() + tx.sign(key1) + tx.sign(key2) + assert tx._signature_map, "signature_map should not be empty after freeze+sign" + body_bytes = next(iter(tx._signature_map.keys())) + sig_pairs = tx._signature_map[body_bytes].sigPair + assert len(sig_pairs) == 2, "Expected 2 signatures for different keys" + pubkey_prefixes = {sp.pubKeyPrefix for sp in sig_pairs} + expected_prefixes = { + key1.public_key().to_bytes_raw(), + key2.public_key().to_bytes_raw(), + } + assert pubkey_prefixes == expected_prefixes, "Signatures should match key1 and key2 exactly" From 739cc995963319cdc947b3f1ede6594ca45d19cf Mon Sep 17 00:00:00 2001 From: Mohit Yadav Date: Mon, 4 May 2026 19:28:20 +0530 Subject: [PATCH 7/7] Delete tests/unit/signature_deduplication_test.py Signed-off-by: Mohit Yadav --- tests/unit/signature_deduplication_test.py | 46 ---------------------- 1 file changed, 46 deletions(-) delete mode 100644 tests/unit/signature_deduplication_test.py diff --git a/tests/unit/signature_deduplication_test.py b/tests/unit/signature_deduplication_test.py deleted file mode 100644 index 7d3ad9af5..000000000 --- a/tests/unit/signature_deduplication_test.py +++ /dev/null @@ -1,46 +0,0 @@ -from __future__ import annotations - -from hiero_sdk_python.account.account_id import AccountId -from hiero_sdk_python.crypto.private_key import PrivateKey -from hiero_sdk_python.tokens.token_id import TokenId -from hiero_sdk_python.tokens.token_mint_transaction import TokenMintTransaction -from hiero_sdk_python.transaction.transaction_id import TransactionId - - -def test_duplicate_signature_not_added(): - tx = TokenMintTransaction() - tx.set_transaction_id(TransactionId.generate(AccountId(0, 0, 1234))) - tx.set_node_account_id(AccountId(0, 0, 3)) - tx.set_token_id(TokenId(0, 0, 1)) - tx.set_amount(100) - key = PrivateKey.generate_ed25519() - tx.freeze() - tx.sign(key) - tx.sign(key) - assert tx._signature_map, "signature_map should not be empty after freeze+sign" # ← ADD HERE - body_bytes = next(iter(tx._signature_map.keys())) - sig_pairs = tx._signature_map[body_bytes].sigPair - assert len(sig_pairs) == 1, "Expected 1 signature for duplicate key" - - -def test_multiple_keys_still_work(): - tx = TokenMintTransaction() - tx.set_transaction_id(TransactionId.generate(AccountId(0, 0, 1234))) - tx.set_node_account_id(AccountId(0, 0, 3)) - tx.set_token_id(TokenId(0, 0, 1)) - tx.set_amount(100) - key1 = PrivateKey.generate_ed25519() - key2 = PrivateKey.generate_ed25519() - tx.freeze() - tx.sign(key1) - tx.sign(key2) - assert tx._signature_map, "signature_map should not be empty after freeze+sign" - body_bytes = next(iter(tx._signature_map.keys())) - sig_pairs = tx._signature_map[body_bytes].sigPair - assert len(sig_pairs) == 2, "Expected 2 signatures for different keys" - pubkey_prefixes = {sp.pubKeyPrefix for sp in sig_pairs} - expected_prefixes = { - key1.public_key().to_bytes_raw(), - key2.public_key().to_bytes_raw(), - } - assert pubkey_prefixes == expected_prefixes, "Signatures should match key1 and key2 exactly"