Skip to content

verifyTypedData() validates signatures with arbitrary length #3593

@stiefn

Description

@stiefn

Check existing issues

Viem Version

2.28.0

Current Behavior

recoverPublicKey() extracts the v value of a signature in the following way:

const yParityOrV = hexToNumber(`0x${signatureHex.slice(130)}`)

Neither the full length of the signature nor the slice are limited in length. This allows to craft signatures with arbitrary length that still verify successfully.

Expected Behavior

Signatures for which no common on-chain library exists (e.g., OpenZeppelin or Solady) that can parse them into the correct format for the EVM precompile should not verify in viem. Off-chain applications might verify such signatures and then relay them on-chain, leading to failure.

Steps To Reproduce

Consider the following example:

import { http, verifyTypedData, createWalletClient } from 'viem'
import { anvil } from 'viem/chains'
 
export const account = '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266'
 
export const walletClient = createWalletClient({
  transport: http(),
  chain: anvil
})

export const domain = {
  name: 'Test',
  version: "1",
  chainId: 31337,
  verifyingContract: '0x0000000000000000000000000000000000000000',
} as const
  
// The named list of all type definitions
export const types = {
  Test: [
    { name: 'test', type: 'string' }
  ],
} as const

const message = {
  test: "Test",
} as const
  
walletClient.signTypedData({
  account,
  domain,
  types,
  primaryType: 'Test',
  message,
}).then((signature) => {
  console.log(signature)
})

This creates the following signature with the private key of the first default account in anvil:

0xa68c42a14d6c8ededb487f667310f66c120546407f86808254178d96372771f26b74212b32e6f91878f0b179d41f8768fe3d7d34a612c29590cc448a1749eda81b

We can modify the last byte by left-padding an arbitrary amount of 0s:

0xa68c42a14d6c8ededb487f667310f66c120546407f86808254178d96372771f26b74212b32e6f91878f0b179d41f8768fe3d7d34a612c29590cc448a1749eda8001b

This modified signature is still verified correctly:

verifyTypedData({
  address: account,
  domain,
  types,
  primaryType: 'Test',
  message,
  signature: "0xa68c42a14d6c8ededb487f667310f66c120546407f86808254178d96372771f26b74212b32e6f91878f0b179d41f8768fe3d7d34a612c29590cc448a1749eda8001b",
}).then((valid) => {
  console.log(valid) // returns true
})

Link to Minimal Reproducible Example

No response

Anything else?

Cannot link a reproducible example as I couldn't find a public RPC that supports eth_signTypedData_v4.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions