Skip to content

Commit

Permalink
docs: signed data
Browse files Browse the repository at this point in the history
  • Loading branch information
jxom committed Oct 29, 2024
1 parent e0d7bab commit c6c52c8
Show file tree
Hide file tree
Showing 7 changed files with 178 additions and 12 deletions.
170 changes: 170 additions & 0 deletions site/pages/guides/signed-data.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
# Signed & Typed Data

## Overview

The [ERC-191 Signed Data](https://eips.ethereum.org/EIPS/eip-191) 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:

| Module | Name | Version |
| ----------------------------------------- | --------------------------------------- | ------- |
| [`PersonalMessage`](/api/PersonalMessage) | Personal Message (aka. `personal_sign`) | `0x45` |
| [`TypedData`](/api/TypedData) | Typed Data | `0x01` |
| [`ValidatorData`](/api/ValidatorData) | Data with intended validator | `0x00` |

## 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](/guides/siwe#create-siwe-message).

A signable Personal Message payload can be computed using [`PersonalMessage.getSignPayload`](/api/PersonalMessage/getSignPayload):

```ts twoslash
import { Hex, PersonalMessage } from 'ox'

const payload = PersonalMessage.getSignPayload(
Hex.fromString('hello world')
)
```

We can then sign the payload by using a [Signer](/guides/ecdsa#signers). For this example, we will use [`Secp256k1.sign`](/api/Secp256k1/sign):

```ts twoslash
import { Hex, PersonalMessage, Secp256k1 } from 'ox'

const payload = PersonalMessage.getSignPayload(
Hex.fromString('hello world')
)

const signature = Secp256k1.sign({ // [!code focus]
payload, // [!code focus]
privateKey: '0x...', // [!code focus]
}) // [!code focus]
```

### Wallets

Most Wallets expose a [`personal_sign` RPC interface](https://docs.metamask.io/wallet/reference/json-rpc-methods/personal_sign/) 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.

```ts twoslash
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({ // [!code focus]
method: 'personal_sign', // [!code focus]
params: [Hex.fromString('hello world'), address], // [!code focus]
}) // [!code focus]
```

## 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](https://eips.ethereum.org/EIPS/eip-712).

A signable Typed Data payload can be computed using [`TypedData.getSignPayload`](/api/TypedData/getSignPayload):

```ts twoslash
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](/guides/ecdsa#signers). For this example, we will use [`Secp256k1.sign`](/api/Secp256k1/sign):

```ts twoslash
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({ // [!code focus]
payload, // [!code focus]
privateKey: '0x...', // [!code focus]
}) // [!code focus]
```

### Wallets

Most Wallets expose a [`eth_signTypedData_v4` RPC interface](https://docs.metamask.io/wallet/reference/json-rpc-methods/eth_signtypeddata_v4/) 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.

```ts twoslash
// @noErrors
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({ /* ... */ }) // [!code focus]

const signature = await provider.request({ // [!code focus]
method: 'eth_signTypedData_v4', // [!code focus]
params: [address, payload], // [!code focus]
}) // [!code focus]
```
6 changes: 1 addition & 5 deletions site/vocs.config.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -81,17 +81,13 @@ export default defineConfig({
link: '/guides/siwe',
},
{
text: 'Signed Data (EIP-191) 🚧',
text: 'Signed & Typed Data',
link: '/guides/signed-data',
},
{
text: 'Transaction Envelopes',
link: '/guides/transaction-envelopes',
},
{
text: 'Typed Data (EIP-712) 🚧',
link: '/guides/typed-data',
},
{ text: 'WebAuthn Signers', link: '/guides/webauthn' },
{ text: 'Using with Effect 🚧', link: '/guides/effect' },
{ text: 'Using with NeverThrow 🚧', link: '/guides/neverthrow' },
Expand Down
6 changes: 3 additions & 3 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1566,12 +1566,12 @@ export type Log<
export * as Mnemonic from './Mnemonic.js'

/**
* Utilities & types for working with [EIP-191 Personal Messages](https://eips.ethereum.org/EIPS/eip-191#version-0x45-e)
* Utilities & types for working with [ERC-191 Personal Messages](https://eips.ethereum.org/EIPS/eip-191#version-0x45-e)
*
* @example
* ### Computing Sign Payload
*
* An EIP-191 personal sign payload can be computed using {@link ox#PersonalMessage.(getSignPayload:function)}:
* An ERC-191 personal sign payload can be computed using {@link ox#PersonalMessage.(getSignPayload:function)}:
*
* ```ts twoslash
* import { Hex, PersonalMessage, Secp256k1 } from 'ox'
Expand Down Expand Up @@ -3173,7 +3173,7 @@ export type TransactionRequest<
export * as TypedData from './TypedData.js'

/**
* Utilities & types for working with [EIP-191 Validator Data](https://eips.ethereum.org/EIPS/eip-191#0x00)
* Utilities & types for working with [ERC-191 Validator Data](https://eips.ethereum.org/EIPS/eip-191#0x00)
*
* @category Signed & Typed Data
*/
Expand Down
2 changes: 1 addition & 1 deletion src/internal/PersonalMessage/encode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { Hex_size } from '../Hex/size.js'
import type { Hex } from '../Hex/types.js'

/**
* Encodes a personal sign message in [EIP-191 format](https://eips.ethereum.org/EIPS/eip-191#version-0x45-e): `0x19 ‖ "Ethereum Signed Message:\n" + message.length ‖ message`.
* Encodes a personal sign message in [ERC-191 format](https://eips.ethereum.org/EIPS/eip-191#version-0x45-e): `0x19 ‖ "Ethereum Signed Message:\n" + message.length ‖ message`.
*
* @example
* ```ts twoslash
Expand Down
2 changes: 1 addition & 1 deletion src/internal/PersonalMessage/getSignPayload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import type { Hex } from '../Hex/types.js'
import { PersonalMessage_encode } from './encode.js'

/**
* Gets the payload to use for signing an [EIP-191 formatted](https://eips.ethereum.org/EIPS/eip-191#version-0x45-e) personal message.
* Gets the payload to use for signing an [ERC-191 formatted](https://eips.ethereum.org/EIPS/eip-191#version-0x45-e) personal message.
*
* @example
* ```ts twoslash
Expand Down
2 changes: 1 addition & 1 deletion src/internal/ValidatorData/encode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { Hex_from } from '../Hex/from.js'
import type { Hex } from '../Hex/types.js'

/**
* Encodes data with a validator in [EIP-191 format](https://eips.ethereum.org/EIPS/eip-191#version-0x00): `0x19 ‖ 0x00 ‖ <intended validator address> ‖ <data to sign>`.
* Encodes data with a validator in [ERC-191 format](https://eips.ethereum.org/EIPS/eip-191#version-0x00): `0x19 ‖ 0x00 ‖ <intended validator address> ‖ <data to sign>`.
*
* @example
* ```ts twoslash
Expand Down
2 changes: 1 addition & 1 deletion src/internal/ValidatorData/getSignPayload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import type { Hex } from '../Hex/types.js'
import { ValidatorData_encode } from './encode.js'

/**
* Gets the payload to use for signing [EIP-191 formatted](https://eips.ethereum.org/EIPS/eip-191#0x00) data with an intended validator.
* Gets the payload to use for signing [ERC-191 formatted](https://eips.ethereum.org/EIPS/eip-191#0x00) data with an intended validator.
*
* @example
* ```ts twoslash
Expand Down

0 comments on commit c6c52c8

Please sign in to comment.