Skip to content

Incorrect recovery identifier “v” when signing EIP-712 messages#134

Closed
NoPreservatives wants to merge 2 commits intoMetaMask:mainfrom
NoPreservatives:patch-1
Closed

Incorrect recovery identifier “v” when signing EIP-712 messages#134
NoPreservatives wants to merge 2 commits intoMetaMask:mainfrom
NoPreservatives:patch-1

Conversation

@NoPreservatives
Copy link
Copy Markdown

@NoPreservatives NoPreservatives commented Dec 22, 2021

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"

**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"
@NoPreservatives NoPreservatives requested a review from a team as a code owner December 22, 2021 12:31
@NoPreservatives NoPreservatives changed the title Update index.js Incorrect recovery identifier “v” when signing EIP-712 messages Dec 22, 2021
@danjm
Copy link
Copy Markdown
Contributor

danjm commented Nov 30, 2023

This was superceded by #152

@danjm danjm closed this Nov 30, 2023
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 -->
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants