Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions src/hiero_sdk_python/transaction/transaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
43 changes: 43 additions & 0 deletions tests/unit/transaction_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand All @@ -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
Expand Down Expand Up @@ -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"
Loading