Skip to content

Signed & Typed Data

Overview

The ERC-191 Signed Data standard defines a specification for handling signed data in Ethereum contracts. By defining a structured format for signable data, ERC-191 helps prevent vulnerabilities associated with obscured transaction signing and multi-signature wallets.

Signable data are prefixed with a version byte (e.g. 0x45 for Personal Messages). This protects an end-user from signing obscured transaction data constructed by a malicious actor and consequently losing funds.

Ox supports the following ERC-191 versions:

ModuleNameVersion
PersonalMessagePersonal Message (aka. personal_sign)0x45
TypedDataTyped Data0x01
ValidatorDataData with intended validator0x00

Personal Messages

Personal messages are a common use-case for ERC-191. They are typically used to sign arbitrary messages that will be displayed to the user, for example, a Sign-In With Ethereum (SIWE) message.

A signable Personal Message payload can be computed using PersonalMessage.getSignPayload:

import { Hex, PersonalMessage } from 'ox'
 
const payload = PersonalMessage.getSignPayload(
  Hex.fromString('hello world')
)

We can then sign the payload by using a Signer. For this example, we will use Secp256k1.sign:

import { Hex, PersonalMessage, Secp256k1 } from 'ox'
 
const payload = PersonalMessage.getSignPayload(
  Hex.fromString('hello world')
)
 
const signature = Secp256k1.sign({ 
  payload, 
  privateKey: '0x...', 
})

Wallets

Most Wallets expose a personal_sign RPC interface that can be used to sign Personal Messages. This means you can use the personal_sign RPC method to sign a message without the ceremony of constructing and signing it yourself.

import 'ox/window'
import { Hex, Provider } from 'ox'
 
const provider = Provider.from(window.ethereum)
 
const [address] = await provider.request({ method: 'eth_requestAccounts' })
 
const signature = await provider.request({ 
  method: 'personal_sign', 
  params: [Hex.fromString('hello world'), address], 
})

Typed Data

Typed Data is another type of signed data that is used to present structured data to the user to sign. This structure (and encoding format) is defined by the EIP-712 standard.

A signable Typed Data payload can be computed using TypedData.getSignPayload:

import { TypedData } from 'ox'
 
const payload = TypedData.getSignPayload({ 
  domain: {
    name: 'Ether Mail',
    version: '1',
    chainId: 1,
    verifyingContract: '0x0000000000000000000000000000000000000000',
  },
  types: {
    Person: [
      { name: 'name', type: 'string' },
      { name: 'wallet', type: 'address' },
    ],
    Mail: [
      { name: 'from', type: 'Person' },
      { name: 'to', type: 'Person' },
      { name: 'contents', type: 'string' },
    ],
  },
  primaryType: 'Mail',
  message: {
    from: {
      name: 'Cow',
      wallet: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826',
    },
    to: {
      name: 'Bob',
      wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB',
    },
    contents: 'Hello, Bob!',
  },
})

We can then sign the payload by using a Signer. For this example, we will use Secp256k1.sign:

import { Hex, Secp256k1, TypedData } from 'ox'
 
const payload = TypedData.getSignPayload({ 
  domain: {
    name: 'Ether Mail',
    version: '1',
    chainId: 1,
    verifyingContract: '0x0000000000000000000000000000000000000000',
  },
  types: {
    Person: [
      { name: 'name', type: 'string' },
      { name: 'wallet', type: 'address' },
    ],
    Mail: [
      { name: 'from', type: 'Person' },
      { name: 'to', type: 'Person' },
      { name: 'contents', type: 'string' },
    ],
  },
  primaryType: 'Mail',
  message: {
    from: {
      name: 'Cow',
      wallet: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826',
    },
    to: {
      name: 'Bob',
      wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB',
    },
    contents: 'Hello, Bob!',
  },
})
 
const signature = Secp256k1.sign({ 
  payload, 
  privateKey: '0x...', 
})

Wallets

Most Wallets expose a eth_signTypedData_v4 RPC interface that can be used to sign Typed Data. This means you can use the eth_signTypedData_v4 RPC method to sign a message without the ceremony of constructing and signing it yourself.

import 'ox/window'
import { Hex, Provider, Secp256k1, TypedData } from 'ox'
 
const provider = Provider.from(window.ethereum)
 
const [address] = await provider.request({ method: 'eth_requestAccounts' })
 
const payload = TypedData.serialize({ /* ... */ })
 
const signature = await provider.request({ 
  method: 'eth_signTypedData_v4', 
  params: [address, payload], 
})

Related Modules

ModuleDescription
PersonalMessageUtilities & types for working with EIP-191 Personal Messages
TypedDataUtility functions for working with EIP-712 Typed Data
ValidatorDataUtilities & types for working with EIP-191 Validator Data