Incorrect recovery identifier “v” when signing EIP-712 messages#134
Closed
NoPreservatives wants to merge 2 commits intoMetaMask:mainfrom
Closed
Incorrect recovery identifier “v” when signing EIP-712 messages#134NoPreservatives wants to merge 2 commits intoMetaMask:mainfrom
NoPreservatives wants to merge 2 commits intoMetaMask:mainfrom
Conversation
**Describe the bug** The Ethereum YellowPaper appendix F, page 25, defines the recovery identifier “v” - the last byte of the signature. It should be either 27 (0x1b) or 28 (0x1c). Metamask signing EIP-712 messages with Ledger always returns "0" or "1" https://ethereum.github.io/yellowpaper/paper.pdf **Steps to reproduce** Sign Typed V4 or Personal message with Ledger. Check result. It will end with "0" or "1" **Expected behavior** Signed Typed and Personal messages result should end with "0x1b" or "0x1c"
Contributor
|
This was superceded by #152 |
This was referenced Jan 29, 2026
github-merge-queue bot
pushed a commit
to MetaMask/accounts
that referenced
this pull request
Jan 30, 2026
…r recovery (#449) ## Summary Ledger devices may return signature `v` as 0 or 1 (modern format), but Ethereum's `ecrecover` expects 27 or 28. This causes `"The signature doesnt match the right address"` errors in [`signPersonalMessage`](https://github.com/MetaMask/accounts/blob/main/packages/keyring-eth-ledger-bridge/src/ledger-keyring.ts#L475) and [`signTypedData`](https://github.com/MetaMask/accounts/blob/main/packages/keyring-eth-ledger-bridge/src/ledger-keyring.ts#L475). ## Fix ```typescript if (recoveryParam === 0 || recoveryParam === 1) { recoveryParam += 27; } ``` ## Why This Wasn't Fixed Before **Original bug (pre-2022):** The code *subtracted* 27 from `v`: ```javascript let v = payload.v - 27 // WRONG ``` This converted correct values (27/28) into incorrect ones (0/1). **PR #152 (August 2022):** Changed to pass through the raw value: ```javascript let v = parseInt(payload.v, 10) // Just pass through ``` This fixed the case where Ledger returns 27/28, but assumed Ledger *always* returns 27/28. **The gap:** Ledger devices may return 0/1 depending on firmware, signing method, or device model. PR #152 didn't normalize these values. **Issue #10850 was closed prematurely** in January 2024 with "I expect this was resolved in PR #152" — but PR #152 only fixed one direction of the problem. ## References This is a well-known issue with a standard fix across the ecosystem: - [Frame wallet](https://github.com/floating/frame/blob/0.6.10/main/provider/helpers.ts#L10) - `v = v === 0 || v === 1 ? v + 27 : v` - [Rabby wallet](https://github.com/RabbyHub/Rabby/blob/develop/src/ui/utils/gnosis.ts) - *"Metamask with ledger returns V=0/1 here too"* - [Gnosis Safe Android](https://github.com/safe-global/safe-android/blob/main/app/src/main/java/io/gnosis/safe/ui/transactions/details/viewmodel/TxReviewViewModel.kt) - `if (v <= 1) v += 27` - Related issues: [eth-ledger-bridge-keyring#134](MetaMask/eth-ledger-bridge-keyring#134), [eth-ledger-bridge-keyring#151](MetaMask/eth-ledger-bridge-keyring#151), [metamask-extension#10850](MetaMask/metamask-extension#10850) <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Changes signature formatting in `signPersonalMessage` and `signTypedData`, which can affect signature verification and downstream consumers if assumptions about `v` differ. Scope is small and covered by new unit tests for multiple `v` cases. > > **Overview** > Fixes Ledger signing to **normalize the recovery param (`v`)** so devices returning modern values `0/1` are converted to legacy `27/28` before building the final signature in `ledger-keyring.ts`. > > Adds focused tests covering `v` normalization for both `signPersonalMessage` and `signTypedData`, and updates the `CHANGELOG.md` entry accordingly (plus minor Jest coverage threshold tweaks). > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit e756502. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Describe the bug
The Ethereum YellowPaper appendix F, page 25, defines the recovery identifier “v” - the last byte of the signature. It should be either 27 (0x1b) or 28 (0x1c). Metamask signing EIP-712 messages with Ledger always returns "0" or "1"
https://ethereum.github.io/yellowpaper/paper.pdf
This causes issues on some applications which are checking for correct signature format.
Steps to reproduce
Sign Typed V4 or Personal message with Ledger. Check result. It will end with "0" or "1"
Sign same V4 and Personal messages with internal, non-Ledger, Metamask account. Result will end with 27 (0x1b) or 28 (0x1c).
Expected behavior
Signed Typed and Personal messages result should end with "0x1b" or "0x1c"