Skip to content

fix: prevent duplicate signatures in _signature_map#2219

Merged
aceppaluni merged 12 commits into
hiero-ledger:mainfrom
mohityadav8:fix/signature-deduplication
May 4, 2026
Merged

fix: prevent duplicate signatures in _signature_map#2219
aceppaluni merged 12 commits into
hiero-ledger:mainfrom
mohityadav8:fix/signature-deduplication

Conversation

@mohityadav8

Copy link
Copy Markdown
Contributor

Description:
Prevent duplicate SignaturePair entries from being added to _signature_map when signing a transaction multiple times with the same key.

  • Add deduplication check based on pubKeyPrefix before appending signatures
  • Ensure only one signature per (body_bytes, pubKeyPrefix) combination
  • Add unit tests for:
    • duplicate signing with same key
    • signing with multiple distinct keys

Related issue(s):

Fixes #2208

Notes for reviewer:

  • The fix ensures no redundant signatures are added
  • Maintains correct behavior for multi-key signing
  • Pre-commit checks pass locally

Checklist

  • Documented (Code comments, README, etc.)
  • Tested (unit, integration, etc.)

@mohityadav8
mohityadav8 requested review from a team as code owners May 1, 2026 13:51
@codacy-production

codacy-production Bot commented May 1, 2026

Copy link
Copy Markdown

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

@coderabbitai

coderabbitai Bot commented May 1, 2026

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Transaction.sign now checks existing signature pairs' pubKeyPrefix for the same transaction body and prevents adding duplicate SignaturePair entries. Two unit tests were added to verify deduplication and that signing with multiple keys still produces multiple entries.

Changes

Signature Deduplication

Layer / File(s) Summary
Data / Shape
src/hiero_sdk_python/transaction/transaction.py
SignatureMap usage remains per-body_bytes; sigPair entries are still stored on that map.
Core Implementation
src/hiero_sdk_python/transaction/transaction.py
Transaction.sign now computes already_signed by scanning existing sigPair.pubKeyPrefix values for a match with the signing public key bytes and only appends the new SignaturePair if no match is found.
Tests
tests/unit/test_signature_deduplication.py, tests/unit/signature_deduplication_test.py
Added test_duplicate_signature_not_added (signing same frozen transaction twice with same PrivateKey yields one sigPair) and test_multiple_keys_still_work (signing with two distinct PrivateKeys yields two sigPairs whose pubKeyPrefix values match the keys' public bytes).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically summarizes the main change: preventing duplicate signatures in the _signature_map by implementing deduplication logic.
Description check ✅ Passed The description is directly related to the changeset, explaining the deduplication mechanism, testing approach, and referencing the linked issue #2208.
Linked Issues check ✅ Passed The code changes fully meet the requirements of issue #2208: a deduplication check based on pubKeyPrefix is added before appending signatures, unit tests verify both duplicate-key and multi-key scenarios, and the fix maintains correct behavior for multiple distinct keys.
Out of Scope Changes check ✅ Passed All changes directly address the scope of issue #2208: the core fix in transaction.py implements the deduplication logic, and test files focus solely on validating this specific functionality.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

📋 Issue Planner

Built with CodeRabbit's Coding Plans for faster development and fewer bugs.

View plan used: #2208

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 30a7e170-2598-402d-8fc2-75740caa0be5

📥 Commits

Reviewing files that changed from the base of the PR and between bcc8417 and aaf08a2.

📒 Files selected for processing (2)
  • src/hiero_sdk_python/transaction/transaction.py
  • tests/unit/test_signature_deduplication.py

Comment thread tests/unit/test_signature_deduplication.py Outdated
@mohityadav8
mohityadav8 force-pushed the fix/signature-deduplication branch 2 times, most recently from de0f4ce to 287e6a5 Compare May 1, 2026 14:16
@mohityadav8

mohityadav8 commented May 1, 2026

Copy link
Copy Markdown
Contributor Author

cc @exploreriii

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/hiero_sdk_python/transaction/transaction.py (1)

182-200: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Hoist the dedup check before private_key.sign() to avoid unnecessary crypto work

public_key_bytes is loop-invariant and private_key.sign() + sig_pair object construction both happen unconditionally before the dedup guard fires. Signing the same key twice per node causes a full crypto operation + proto allocation that is immediately discarded.

♻️ Proposed refactor
-        # We sign the bodies for each node in case we need to switch nodes during execution.
-        for body_bytes in self._transaction_body_bytes.values():
-            signature = private_key.sign(body_bytes)
-
-            public_key_bytes = private_key.public_key().to_bytes_raw()
-
-            if private_key.is_ed25519():
-                sig_pair = basic_types_pb2.SignaturePair(pubKeyPrefix=public_key_bytes, ed25519=signature)
-            else:
-                sig_pair = basic_types_pb2.SignaturePair(pubKeyPrefix=public_key_bytes, ECDSA_secp256k1=signature)
-
-            # 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())
-
-            # 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)
+        public_key_bytes = private_key.public_key().to_bytes_raw()
+
+        # We sign the bodies for each node in case we need to switch nodes during execution.
+        for body_bytes in self._transaction_body_bytes.values():
+            self._signature_map.setdefault(body_bytes, basic_types_pb2.SignatureMap())
+
+            # Skip if this key has already signed this body
+            if any(sp.pubKeyPrefix == public_key_bytes for sp in self._signature_map[body_bytes].sigPair):
+                continue
+
+            signature = private_key.sign(body_bytes)
+
+            if private_key.is_ed25519():
+                sig_pair = basic_types_pb2.SignaturePair(pubKeyPrefix=public_key_bytes, ed25519=signature)
+            else:
+                sig_pair = basic_types_pb2.SignaturePair(pubKeyPrefix=public_key_bytes, ECDSA_secp256k1=signature)
+
+            self._signature_map[body_bytes].sigPair.append(sig_pair)

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 3856665f-4072-4ef9-9a3d-00a5fb1b4bdf

📥 Commits

Reviewing files that changed from the base of the PR and between aaf08a2 and 473de8a.

📒 Files selected for processing (2)
  • src/hiero_sdk_python/transaction/transaction.py
  • tests/unit/test_signature_deduplication.py

Comment thread tests/unit/test_signature_deduplication.py Outdated
Comment thread tests/unit/test_signature_deduplication.py Outdated
@mohityadav8
mohityadav8 force-pushed the fix/signature-deduplication branch from 473de8a to f8b8363 Compare May 2, 2026 09:07

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/hiero_sdk_python/transaction/transaction.py (1)

182-200: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Skip cryptographic signing when the key is already present.

private_key.sign(body_bytes) is computed before deduplication; moving it after the already_signed check avoids unnecessary signing work on repeated calls.

♻️ Proposed refactor
-        for body_bytes in self._transaction_body_bytes.values():
-            signature = private_key.sign(body_bytes)
-
-            public_key_bytes = private_key.public_key().to_bytes_raw()
-
-            if private_key.is_ed25519():
-                sig_pair = basic_types_pb2.SignaturePair(pubKeyPrefix=public_key_bytes, ed25519=signature)
-            else:
-                sig_pair = basic_types_pb2.SignaturePair(pubKeyPrefix=public_key_bytes, ECDSA_secp256k1=signature)
+        public_key_bytes = private_key.public_key().to_bytes_raw()
+        is_ed25519 = private_key.is_ed25519()
+        for body_bytes in self._transaction_body_bytes.values():
             # 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())
 
             # 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:
+                signature = private_key.sign(body_bytes)
+                if is_ed25519:
+                    sig_pair = basic_types_pb2.SignaturePair(pubKeyPrefix=public_key_bytes, ed25519=signature)
+                else:
+                    sig_pair = basic_types_pb2.SignaturePair(pubKeyPrefix=public_key_bytes, ECDSA_secp256k1=signature)
                 self._signature_map[body_bytes].sigPair.append(sig_pair)

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: d4a50307-2bbc-4c25-a259-c6dac726b38a

📥 Commits

Reviewing files that changed from the base of the PR and between 473de8a and f8b8363.

📒 Files selected for processing (2)
  • src/hiero_sdk_python/transaction/transaction.py
  • tests/unit/test_signature_deduplication.py

Comment thread tests/unit/test_signature_deduplication.py Outdated
Signed-off-by: Mohit Yadav <ymohit799057@gmail.com>
@mohityadav8
mohityadav8 force-pushed the fix/signature-deduplication branch from afe9691 to dbd9743 Compare May 2, 2026 09:31
@mohityadav8

Copy link
Copy Markdown
Contributor Author

Hi @Akshat8510 @nadineloepfe , I’ve addressed all the previous issues.

Fixed DCO check (commit is now signed)
Resolved all pre-commit and ruff formatting issues
Updated tests to ensure correct behavior for both duplicate and multi-key signing cases
Verified that all checks are passing locally

The implementation now ensures no duplicate SignaturePair entries are added while preserving correct multi-key signing behavior.

Requesting review.

@github-actions

github-actions Bot commented May 2, 2026

Copy link
Copy Markdown

Hi, this is WorkflowBot.
Your pull request cannot be merged as it is not passing all our workflow checks.
Please click on each check to review the logs and resolve issues so all checks pass.
To help you:

Signed-off-by: Mohit Yadav <ymohit799057@gmail.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/hiero_sdk_python/transaction/transaction.py (1)

183-200: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Move the deduplication check before the cryptographic sign operation.

private_key.sign(body_bytes) (an expensive crypto operation) is executed unconditionally before the dedup guard. When the same key signs twice — the exact bug this PR fixes — a full signature is computed and immediately discarded. Moving the check before sign() and hoisting public_key_bytes out of the loop eliminates the wasted work.

⚡ Proposed fix
-        for body_bytes in self._transaction_body_bytes.values():
-            signature = private_key.sign(body_bytes)
-
-            public_key_bytes = private_key.public_key().to_bytes_raw()
-
-            if private_key.is_ed25519():
-                sig_pair = basic_types_pb2.SignaturePair(pubKeyPrefix=public_key_bytes, ed25519=signature)
-            else:
-                sig_pair = basic_types_pb2.SignaturePair(pubKeyPrefix=public_key_bytes, ECDSA_secp256k1=signature)
-
-            # 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())
-
-            # 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)
+        public_key_bytes = private_key.public_key().to_bytes_raw()
+
+        for body_bytes in self._transaction_body_bytes.values():
+            # Initialize signature map and skip signing if key is already present
+            self._signature_map.setdefault(body_bytes, basic_types_pb2.SignatureMap())
+
+            if any(sp.pubKeyPrefix == public_key_bytes for sp in self._signature_map[body_bytes].sigPair):
+                continue
+
+            signature = private_key.sign(body_bytes)
+
+            if private_key.is_ed25519():
+                sig_pair = basic_types_pb2.SignaturePair(pubKeyPrefix=public_key_bytes, ed25519=signature)
+            else:
+                sig_pair = basic_types_pb2.SignaturePair(pubKeyPrefix=public_key_bytes, ECDSA_secp256k1=signature)
+
+            self._signature_map[body_bytes].sigPair.append(sig_pair)

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 3b2ae8e8-56a2-4f5f-8779-728523139ae2

📥 Commits

Reviewing files that changed from the base of the PR and between f8b8363 and 6b1bc8c.

📒 Files selected for processing (3)
  • src/hiero_sdk_python/transaction/transaction.py
  • tests/unit/signature_deduplication_test.py
  • tests/unit/test_signature_deduplication.py

Comment thread tests/unit/signature_deduplication_test.py Outdated
Comment thread tests/unit/signature_deduplication_test.py Outdated
@mohityadav8
mohityadav8 force-pushed the fix/signature-deduplication branch from 78919e2 to 496afbc Compare May 2, 2026 12:04
Signed-off-by: Mohit Yadav <ymohit799057@gmail.com>
@mohityadav8
mohityadav8 force-pushed the fix/signature-deduplication branch from 496afbc to 03023fd Compare May 2, 2026 12:06

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 007af472-df85-4649-9d4e-3a764b62607f

📥 Commits

Reviewing files that changed from the base of the PR and between 6b1bc8c and 496afbc.

📒 Files selected for processing (1)
  • tests/unit/signature_deduplication_test.py

Comment thread tests/unit/signature_deduplication_test.py Outdated
Signed-off-by: Mohit Yadav <ymohit799057@gmail.com>
@mohityadav8
mohityadav8 force-pushed the fix/signature-deduplication branch from a502268 to e368d7c Compare May 2, 2026 12:12
@mohityadav8

Copy link
Copy Markdown
Contributor Author

@MonaaEid ready for reveiw

@exploreriii exploreriii added step: 1st 1st stage of the review approval process reviewer: committer request review help from a committer labels May 3, 2026
Comment thread tests/unit/signature_deduplication_test.py Outdated
@mohityadav8
mohityadav8 requested a review from manishdait May 4, 2026 14:00
@codecov

codecov Bot commented May 4, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

Impacted file tree graph

@@           Coverage Diff           @@
##             main    #2219   +/-   ##
=======================================
  Coverage   93.73%   93.73%           
=======================================
  Files         145      145           
  Lines        9480     9482    +2     
=======================================
+ Hits         8886     8888    +2     
  Misses        594      594           
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@manishdait manishdait left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm, Please update the branch

@manishdait manishdait added step: 2nd second stage of the review approval process and removed step: 1st 1st stage of the review approval process labels May 4, 2026
@mohityadav8

Copy link
Copy Markdown
Contributor Author

@manishdait i think the failing ClusterFuzzLite job seems to be due to an external package fetch/network issue, not related to this chang...... Re-running CI should resolve it

@aceppaluni
aceppaluni merged commit 306717b into hiero-ledger:main May 4, 2026
30 of 31 checks passed
NssGourav pushed a commit to NssGourav/hiero-sdk-python that referenced this pull request May 14, 2026
Signed-off-by: Mohit Yadav <ymohit799057@gmail.com>
@exploreriii exploreriii added this to the v0.2.7 milestone May 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

reviewer: committer request review help from a committer step: 2nd second stage of the review approval process

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Duplicate signatures added to _signature_map due to missing deduplication check

4 participants