Skip to content

feat: fix eip1559 recovery id#1605#1649

Open
Adityarya11 wants to merge 5 commits into
hiero-ledger:mainfrom
Adityarya11:feat/wire-eip1559-recovery-id#1605
Open

feat: fix eip1559 recovery id#1605#1649
Adityarya11 wants to merge 5 commits into
hiero-ledger:mainfrom
Adityarya11:feat/wire-eip1559-recovery-id#1605

Conversation

@Adityarya11

@Adityarya11 Adityarya11 commented May 14, 2026

Copy link
Copy Markdown
Contributor

Description

This PR wires EIP-1559 signing in the SDK so the recovery ID is computed and serialized correctly for Ethereum transactions.

Changes

  • Add EthereumTransaction::sign() to compute r, s, and recovery ID, and update the encoded transaction.
  • Add EthereumTransactionDataEip1559::toSignBytes() to build the signing preimage.
  • Update the integration test to compute recovery ID dynamically (no hardcoded 01).
  • Add a unit test to validate yParity/r/s placement in the serialized bytes.

Related issue(s)
Fixes #1605


Notes for reviewer


Checklist

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

Summary by CodeRabbit

  • New Features

    • Added support for signing Ethereum EIP-1559 transactions with private keys.
    • Signing now computes and stores signature components and the recovery identifier so signed transactions are produced end-to-end.
  • Tests

    • Re-enabled an integration test covering signer behavior and transaction receipts.
    • Added unit tests verifying signature and recovery ID encoding in transaction data.

Review Change Stack

@Adityarya11 Adityarya11 requested review from a team as code owners May 14, 2026 07:09
@Adityarya11 Adityarya11 requested a review from gsstoykov May 14, 2026 07:09
@github-actions

Copy link
Copy Markdown

Hey @Adityarya11 👋 thanks for the PR!
I'm your friendly PR Helper Bot 🤖 and I'll be riding shotgun on this one, keeping track of your PR's status to help you get it approved and merged.

This comment updates automatically as you push changes -- think of it as your PR's live scoreboard!
Here's the latest:


PR Checks

DCO Sign-off -- All commits have valid sign-offs. Nice work!


GPG Signature -- All commits have verified GPG signatures. Locked and loaded!


Merge Conflicts -- No merge conflicts detected. Smooth sailing!


Issue Link -- Linked to #1605 (assigned to you).


🎉 All checks passed! Your PR is ready for review. Great job!

@github-actions github-actions Bot added the status: needs review The pull request is ready for maintainer review label May 14, 2026
@Adityarya11 Adityarya11 force-pushed the feat/wire-eip1559-recovery-id#1605 branch from 2d16128 to aff9185 Compare May 14, 2026 07:15
@Adityarya11 Adityarya11 marked this pull request as draft May 14, 2026 09:36
Signed-off-by: Aditya Arya <arya050411@gmail.com>
Signed-off-by: Aditya Arya <arya050411@gmail.com>
Signed-off-by: Aditya Arya <arya050411@gmail.com>
Signed-off-by: Aditya Arya <arya050411@gmail.com>
@Adityarya11 Adityarya11 force-pushed the feat/wire-eip1559-recovery-id#1605 branch from 0aee1c0 to f89cbfb Compare May 21, 2026 04:09
@coderabbitai

coderabbitai Bot commented May 21, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Adds EIP-1559 signing: a new public EthereumTransaction::sign(...) method, EthereumTransactionDataEip1559::toSignBytes(), signing implementation that computes r/s and recovery id for EIP-1559, and integration/unit test updates to verify dynamic recovery-id and serialized placement.

Changes

EIP-1559 Ethereum Transaction Signing

Layer / File(s) Summary
EIP-1559 signing preimage
src/sdk/main/include/EthereumTransactionDataEip1559.h, src/sdk/main/src/EthereumTransactionDataEip1559.cc
Adds toSignBytes() returning the RLP-encoded EIP-1559 signing payload (chainId, nonce, maxPriorityGas, maxGas, gasLimit, to, value, callData, accessList) prefixed by the 0x02 type byte.
EthereumTransaction signing contract and implementation
src/sdk/main/include/EthereumTransaction.h, src/sdk/main/src/EthereumTransaction.cc
Declares EthereumTransaction::sign(const std::shared_ptr<PrivateKey>&) and implements signing flow: null-key check, base Transaction::sign(), when using ECDSAsecp256k1PrivateKey and EIP-1559 data compute preimage, produce ECDSA signature, derive r/s and recovery id, populate mR/mS/mRecoveryId, re-serialize, and regenerate signed transaction bytes.
Integration and unit test verification
src/sdk/tests/integration/EthereumTransactionIntegrationTests.cc, src/sdk/tests/unit/EthereumTransactionDataEip1559UnitTests.cc
Enables and updates integration test to compute recovery ID via getRecoveryId(...) and validate contract receipt; adds unit test and includes to assert serialized RLP places mRecoveryId, mR, and mS at RLP indices 9–11.

Estimated Code Review Effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

"A rabbit signs with nimble paws tonight, 🐇
RLP and preimages glowing bright,
r and s and recovery dance in tune,
0x02 leads the melody beneath the moon,
Ledger hums — the signed tx takes flight."

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly indicates EIP-1559 recovery ID implementation work, matching the primary objective of computing and serializing recovery IDs for Ethereum transactions.
Linked Issues check ✅ Passed All coding objectives from issue #1605 are addressed: mRecoveryId is populated via getRecoveryId(), included in serialized bytes, toSignBytes() is implemented, hardcoded recovery ID is replaced with computed value in tests, and unit tests validate yParity/r/s placement.
Out of Scope Changes check ✅ Passed All changes are scoped to EthereumTransaction/EthereumTransactionDataEip1559 direct path as required; EthereumTransactionDataLegacy and EthereumFlow are not modified, and no unrelated changes are present.
Docstring Coverage ✅ Passed Docstring coverage is 84.62% which is sufficient. The required threshold is 80.00%.

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

✨ 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.

@Adityarya11 Adityarya11 marked this pull request as ready for review May 21, 2026 04:16

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/sdk/main/src/EthereumTransaction.cc`:
- Around line 91-100: The code currently only checks for negative recovery IDs
from ecdsaKey->getRecoveryId but allows values >1 to be serialized into EIP-1559
yParity; update the check after const int recId = ecdsaKey->getRecoveryId(r, s,
messageToSign) to ensure recId is exactly 0 or 1 (throw std::runtime_error for
any other value), then assign eip1559Data->mRecoveryId using the validated recId
(static_cast<std::byte>(recId)) before calling mEthereumData =
eip1559Data->toBytes(); this ensures mRecoveryId holds a valid yParity and
prevents invalid serialization.

In `@src/sdk/main/src/EthereumTransactionDataEip1559.cc`:
- Around line 103-105: The EIP-1559 encoding paths currently hardcode an empty
access list (the RLPItem pushBack and the concatenation with 0x02), so
transactions with a non-empty mAccessList are encoded/hashed incorrectly; update
both toSignBytes() and toBytes() in EthereumTransactionDataEip1559 (replace the
placeholder empty access list RLPItem and the hardcoded concatenation with logic
that RLP-encodes mAccessList and appends it to the RLP list before returning the
concatenated 0x02 payload) — ensure you use the existing mAccessList member and
the same RLP encoding helpers used elsewhere to produce the correct access list
encoding.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0f339ba5-cbb6-420f-b076-36fccf7464a1

📥 Commits

Reviewing files that changed from the base of the PR and between a898153 and f89cbfb.

📒 Files selected for processing (6)
  • src/sdk/main/include/EthereumTransaction.h
  • src/sdk/main/include/EthereumTransactionDataEip1559.h
  • src/sdk/main/src/EthereumTransaction.cc
  • src/sdk/main/src/EthereumTransactionDataEip1559.cc
  • src/sdk/tests/integration/EthereumTransactionIntegrationTests.cc
  • src/sdk/tests/unit/EthereumTransactionDataEip1559UnitTests.cc

Comment thread src/sdk/main/src/EthereumTransaction.cc
Comment thread src/sdk/main/src/EthereumTransactionDataEip1559.cc Outdated
Signed-off-by: Aditya Arya <arya050411@gmail.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

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

⚠️ Outside diff range comments (1)
src/sdk/main/src/EthereumTransaction.cc (1)

56-64: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Finalize the EIP-1559 payload before calling the base sign().

Transaction<EthereumTransaction>::sign(key) runs before parsing mEthereumData, generating the ECDSA signature, and validating recId. If anything throws on Lines 72-95, sign() exits with *this already mutated by the base signing path even though Line 100 never writes the finalized EIP-1559 bytes.

Also applies to: 72-100

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/sdk/main/src/EthereumTransaction.cc` around lines 56 - 64, The current
EthereumTransaction::sign(const std::shared_ptr<PrivateKey>&) calls
Transaction<EthereumTransaction>::sign(key) before finalizing the EIP-1559
payload, which lets the base sign mutate *this* if subsequent EIP-1559
parsing/signature/recId validation (mEthereumData handling between lines
~72-100) throws; fix by moving/performing the EIP-1559 finalization (parse
mEthereumData, compute ECDSA signature bytes, validate recId and produce the
finalized EIP-1559 payload bytes) prior to invoking
Transaction<EthereumTransaction>::sign(key), or alternatively defer any mutation
in the base path until after finalization so that EthereumTransaction::sign only
calls Transaction<...>::sign once the EIP-1559 bytes are finalized and safe to
attach to *this*.
♻️ Duplicate comments (1)
src/sdk/main/src/EthereumTransactionDataEip1559.cc (1)

92-107: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Keep toBytes() consistent with the new signing preimage.

toSignBytes() now includes mAccessList, but toBytes() still hardcodes an empty list at Line 83. That means any transaction with a non-empty access list is signed as one payload and serialized/submitted as another once EthereumTransaction::sign() rebuilds mEthereumData.

Suggested fix
 std::vector<std::byte> EthereumTransactionDataEip1559::toBytes() const
 {
   RLPItem list(RLPItem::RLPType::LIST_TYPE);
   list.pushBack(mChainId);
   list.pushBack(mNonce);
   list.pushBack(mMaxPriorityGas);
   list.pushBack(mMaxGas);
   list.pushBack(mGasLimit);
   list.pushBack(mTo);
   list.pushBack(mValue);
   list.pushBack(mCallData);
-  list.pushBack(RLPItem());
+  RLPItem accessListItem(mAccessList);
+  accessListItem.setType(RLPItem::RLPType::LIST_TYPE);
+  list.pushBack(accessListItem);
   list.pushBack(mRecoveryId);
   list.pushBack(mR);
   list.pushBack(mS);

   return internal::Utilities::concatenateVectors({ { std::byte(0x02) }, list.write() });
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/sdk/main/src/EthereumTransactionDataEip1559.cc` around lines 92 - 107,
The toBytes() serialization must match toSignBytes() by including the actual
access list instead of the hardcoded empty list; update the
EthereumTransactionDataEip1559::toBytes() implementation to push mAccessList (or
an RLP LIST_TYPE-wrapped mAccessList like in toSignBytes()) into the RLP list so
serialized payload equals the signing preimage used by
EthereumTransaction::sign(), and ensure any RLPItem construction and setType
calls mirror the pattern used in toSignBytes() for mAccessList to avoid
mismatched signatures when rebuilding mEthereumData.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/sdk/main/src/EthereumTransaction.cc`:
- Around line 56-64: The current EthereumTransaction::sign(const
std::shared_ptr<PrivateKey>&) calls Transaction<EthereumTransaction>::sign(key)
before finalizing the EIP-1559 payload, which lets the base sign mutate *this*
if subsequent EIP-1559 parsing/signature/recId validation (mEthereumData
handling between lines ~72-100) throws; fix by moving/performing the EIP-1559
finalization (parse mEthereumData, compute ECDSA signature bytes, validate recId
and produce the finalized EIP-1559 payload bytes) prior to invoking
Transaction<EthereumTransaction>::sign(key), or alternatively defer any mutation
in the base path until after finalization so that EthereumTransaction::sign only
calls Transaction<...>::sign once the EIP-1559 bytes are finalized and safe to
attach to *this*.

---

Duplicate comments:
In `@src/sdk/main/src/EthereumTransactionDataEip1559.cc`:
- Around line 92-107: The toBytes() serialization must match toSignBytes() by
including the actual access list instead of the hardcoded empty list; update the
EthereumTransactionDataEip1559::toBytes() implementation to push mAccessList (or
an RLP LIST_TYPE-wrapped mAccessList like in toSignBytes()) into the RLP list so
serialized payload equals the signing preimage used by
EthereumTransaction::sign(), and ensure any RLPItem construction and setType
calls mirror the pattern used in toSignBytes() for mAccessList to avoid
mismatched signatures when rebuilding mEthereumData.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8eb0d080-46b1-4394-8165-6b21ec0bd600

📥 Commits

Reviewing files that changed from the base of the PR and between f89cbfb and c5a8cac.

📒 Files selected for processing (2)
  • src/sdk/main/src/EthereumTransaction.cc
  • src/sdk/main/src/EthereumTransactionDataEip1559.cc

@Adityarya11

Copy link
Copy Markdown
Contributor Author

Hi @rwalworth,
this PR is ready for final review. I would appreciate it if you could take a look when you have a moment.
Thanks!

@Adityarya11 Adityarya11 requested a review from rwalworth May 29, 2026 03:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

status: needs review The pull request is ready for maintainer review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Intermediate]: Populate EthereumTransactionDataEip1559::mRecoveryId via EthereumFlow

1 participant