Skip to content

Commit

Permalink
feat: add ability to hash data representation of message (#610)
Browse files Browse the repository at this point in the history
* feat: add ability to hash data representation of message

Co-authored-by: moldy <[email protected]>

* chore: changeset

* feat: ethers adapter

---------

Co-authored-by: moldy <[email protected]>
  • Loading branch information
jxom and moldy530 authored Jun 1, 2023
1 parent 2695f0d commit 06ee89c
Show file tree
Hide file tree
Showing 19 changed files with 305 additions and 26 deletions.
11 changes: 11 additions & 0 deletions .changeset/two-candles-sit.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
"viem": patch
---

Added ability to hash data representation of `message` via a `raw` attribute in `signMessage`, `verifyMessage`, `recoverMessageAddress`.

```ts
await walletClient.signMessage({
message: { raw: '0x68656c6c6f20776f726c64' }
})
```
19 changes: 16 additions & 3 deletions site/docs/actions/public/verifyMessage.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ Wheather the signed message is valid for the given address.
The Ethereum address that signed the original message.

```ts
const valid = await verifyMessage({
const valid = await publicClient.verifyMessage({
address: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', // [!code focus:1]
message: 'hello world',
signature:
Expand All @@ -89,23 +89,36 @@ const valid = await verifyMessage({

The message to be verified.

By default, viem verifies the UTF-8 representation of the message.

```ts
const valid = await verifyMessage({
const valid = await publicClient.verifyMessage({
address: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266',
message: 'hello world', // [!code focus:1]
signature:
'0x66edc32e2ab001213321ab7d959a2207fcef5190cc9abb6da5b0d2a8a9af2d4d2b0700e2c317c4106f337fd934fbbb0bf62efc8811a78603b33a8265d3b8f8cb1c',
})
```

To verify the data representation of the message, you can use the `raw` attribute.

```ts
const valid = await publicClient.verifyMessage({
address: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266',
message: { raw: '0x68656c6c6f20776f726c64' }, // [!code focus:1]
signature:
'0x66edc32e2ab001213321ab7d959a2207fcef5190cc9abb6da5b0d2a8a9af2d4d2b0700e2c317c4106f337fd934fbbb0bf62efc8811a78603b33a8265d3b8f8cb1c',
})
```

### signature

- **Type:** `Hex | ByteArray`

The signature that was generated by signing the message with the address's signer.

```ts
const valid = await verifyMessage({
const valid = await publicClient.verifyMessage({
address: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266',
message: 'hello world',
signature: // [!code focus:2]
Expand Down
20 changes: 19 additions & 1 deletion site/docs/actions/wallet/signMessage.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,13 @@ const signature = await walletClient.signMessage({ // [!code focus:99]
message: 'hello world',
})
// "0xa461f509887bd19e312c0c58467ce8ff8e300d3c1a90b608a760c5b80318eaf15fe57c96f9175d6cd4daad4663763baa7e78836e067d0163e9a2ccf2ff753f5b1b"

const signature = await walletClient.signMessage({
account,
// Hex data representation of message.
message: { raw: '0x68656c6c6f20776f726c64' },
})
// "0xa461f509887bd19e312c0c58467ce8ff8e300d3c1a90b608a760c5b80318eaf15fe57c96f9175d6cd4daad4663763baa7e78836e067d0163e9a2ccf2ff753f5b1b"
```

```ts [config.ts]
Expand Down Expand Up @@ -120,17 +127,28 @@ const signature = await walletClient.signMessage({

### data

- **Type:** `string`
- **Type:** `string | { raw: Hex | ByteArray }`

Message to sign.

By default, viem signs the UTF-8 representation of the message.

```ts
const signature = await walletClient.signMessage({
account: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266',
message: 'hello world', // [!code focus:1]
})
```

To sign the data representation of the message, you can use the `raw` attribute.

```ts
const signature = await walletClient.signMessage({
account: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266',
message: { raw: '0x68656c6c6f20776f726c64' }, // [!code focus:1]
})
```

## JSON-RPC Methods

- JSON-RPC Accounts:
Expand Down
13 changes: 12 additions & 1 deletion site/docs/utilities/hashMessage.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,17 @@ import { hashMessage } from 'viem'

hashMessage('hello world') // [!code focus:2]
// 0xd9eba16ed0ecae432b71fe008c98cc872bb4cc214d3220a36f365326cf807d68

// Hash a hex data value. // [!code focus:3]
hashMessage({ raw: '0x68656c6c6f20776f726c64' })
// 0xd9eba16ed0ecae432b71fe008c98cc872bb4cc214d3220a36f365326cf807d68

// Hash a bytes data value. // [!code focus:6]
hashMessage({
raw: Uint8Array.from([
104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100,
])})
// 0xd9eba16ed0ecae432b71fe008c98cc872bb4cc214d3220a36f365326cf807d68
```

## Returns
Expand All @@ -43,6 +54,6 @@ The hashed message.

Message to hash.

- **Type:** `string`
- **Type:** `string | { raw: Hex | ByteArray }`


13 changes: 12 additions & 1 deletion site/docs/utilities/recoverMessageAddress.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,17 +62,28 @@ The signing address.

### message

- **Type:** `string`
- **Type:** `string | { raw: Hex | ByteArray }`

The message that was signed.

By default, viem verifies the UTF-8 representation of the message.

```ts
const address = await recoverMessageAddress({
message: 'hello world', // [!code focus]
signature: '0x66edc32e2ab001213321ab7d959a2207fcef5190cc9abb6da5b0d2a8a9af2d4d2b0700e2c317c4106f337fd934fbbb0bf62efc8811a78603b33a8265d3b8f8cb1c'
})
```

To verify the data representation of the message, you can use the `raw` attribute.

```ts
const address = await recoverMessageAddress({
message: { raw: '0x68656c6c6f20776f726c64' }, // [!code focus:1]
signature: '0x66edc32e2ab001213321ab7d959a2207fcef5190cc9abb6da5b0d2a8a9af2d4d2b0700e2c317c4106f337fd934fbbb0bf62efc8811a78603b33a8265d3b8f8cb1c'
})
```

### signature

- **Type:** `Hex | ByteArray`
Expand Down
13 changes: 13 additions & 0 deletions site/docs/utilities/verifyMessage.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ const valid = await verifyMessage({

The message to be verified.

By default, viem signs the UTF-8 representation of the message.

```ts
const valid = await verifyMessage({
address: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266',
Expand All @@ -95,6 +97,17 @@ const valid = await verifyMessage({
})
```

To sign the data representation of the message, you can use the `raw` attribute.

```ts
const valid = await verifyMessage({
address: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266',
message: { raw: '0x68656c6c6f20776f726c64' }, // [!code focus:1]
signature:
'0x66edc32e2ab001213321ab7d959a2207fcef5190cc9abb6da5b0d2a8a9af2d4d2b0700e2c317c4106f337fd934fbbb0bf62efc8811a78603b33a8265d3b8f8cb1c',
})
```

### signature

- **Type:** `Hex | ByteArray`
Expand Down
4 changes: 2 additions & 2 deletions src/accounts/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { HDKey } from '@scure/bip32'

import type { Address, TypedData } from 'abitype'

import type { Hash, Hex } from '../types/misc.js'
import type { Hash, Hex, SignableMessage } from '../types/misc.js'
import type { TransactionSerializable } from '../types/transaction.js'
import type { TypedDataDefinition } from '../types/typedData.js'

Expand All @@ -13,7 +13,7 @@ export type Account<TAddress extends Address = Address> =
export type AccountSource = Address | CustomSource
export type CustomSource = {
address: Address
signMessage: ({ message }: { message: string }) => Promise<Hash>
signMessage: ({ message }: { message: SignableMessage }) => Promise<Hash>
signTransaction: (transaction: TransactionSerializable) => Promise<Hash>
signTypedData: <
TTypedData extends TypedData | { [key: string]: unknown },
Expand Down
24 changes: 24 additions & 0 deletions src/accounts/utils/signMessage.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,27 @@ test('default', async () => {
'"0x05c99bbbe9fac3ad61721a815d19d6771ad39f3e8dffa7ae7561358f20431d8e7f9e1d487c77355790c79c6eb0b0d63690f690615ef99ee3e4f25eef0317d0701b"',
)
})

test('raw', async () => {
expect(
await signMessage({
message: { raw: '0x68656c6c6f20776f726c64' },
privateKey: accounts[0].privateKey,
}),
).toMatchInlineSnapshot(
'"0xa461f509887bd19e312c0c58467ce8ff8e300d3c1a90b608a760c5b80318eaf15fe57c96f9175d6cd4daad4663763baa7e78836e067d0163e9a2ccf2ff753f5b1b"',
)

expect(
await signMessage({
message: {
raw: Uint8Array.from([
104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100,
]),
},
privateKey: accounts[0].privateKey,
}),
).toMatchInlineSnapshot(
'"0xa461f509887bd19e312c0c58467ce8ff8e300d3c1a90b608a760c5b80318eaf15fe57c96f9175d6cd4daad4663763baa7e78836e067d0163e9a2ccf2ff753f5b1b"',
)
})
4 changes: 2 additions & 2 deletions src/accounts/utils/signMessage.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import type { Hex } from '../../types/misc.js'
import type { Hex, SignableMessage } from '../../types/misc.js'
import { hashMessage } from '../../utils/signature/hashMessage.js'

import { sign } from './sign.js'
import { signatureToHex } from './signatureToHex.js'

export type SignMessageParameters = {
/** The message to sign. */
message: string
message: SignableMessage
/** The private key to sign with. */
privateKey: Hex
}
Expand Down
15 changes: 13 additions & 2 deletions src/actions/public/verifyMessage.test.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { describe, expect, test } from 'vitest'

import { smartAccountConfig } from '../../_test/abis.js'
import { address } from '../../_test/constants.js'
import { accounts, address } from '../../_test/constants.js'
import { publicClientMainnet } from '../../_test/utils.js'
import { verifyMessage } from './verifyMessage.js'

describe('verifyMessage', async () => {
describe('verifyMessage', () => {
test('valid signature', async () => {
expect(
await verifyMessage(publicClientMainnet, {
Expand Down Expand Up @@ -37,4 +37,15 @@ describe('verifyMessage', async () => {
}),
).toBe(false)
})

test('raw message', async () => {
expect(
await verifyMessage(publicClientMainnet, {
address: accounts[0].address,
message: { raw: '0x68656c6c6f20776f726c64' },
signature:
'0xa461f509887bd19e312c0c58467ce8ff8e300d3c1a90b608a760c5b80318eaf15fe57c96f9175d6cd4daad4663763baa7e78836e067d0163e9a2ccf2ff753f5b1b',
}),
).toBe(true)
})
})
4 changes: 2 additions & 2 deletions src/actions/public/verifyMessage.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { Chain } from '../../chains.js'
import type { PublicClient } from '../../clients/createPublicClient.js'
import type { Transport } from '../../clients/transports/createTransport.js'
import type { ByteArray, Hex } from '../../types/misc.js'
import type { ByteArray, Hex, SignableMessage } from '../../types/misc.js'
import { hashMessage } from '../../utils/index.js'
import { type VerifyHashParameters, verifyHash } from './verifyHash.js'
import type { Address } from 'abitype'
Expand All @@ -10,7 +10,7 @@ export type VerifyMessageParameters = Omit<VerifyHashParameters, 'hash'> & {
/** The address that signed the original message. */
address: Address
/** The message to be verified. */
message: string
message: SignableMessage
/** The signature that was generated by signing the message with the address's private key. */
signature: Hex | ByteArray
}
Expand Down
23 changes: 23 additions & 0 deletions src/actions/wallet/signMessage.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,29 @@ test('default', async () => {
)
})

test('raw', async () => {
expect(
await signMessage(walletClient!, {
account: accounts[0].address,
message: { raw: '0x68656c6c6f20776f726c64' },
}),
).toMatchInlineSnapshot(
'"0xa461f509887bd19e312c0c58467ce8ff8e300d3c1a90b608a760c5b80318eaf15fe57c96f9175d6cd4daad4663763baa7e78836e067d0163e9a2ccf2ff753f5b1b"',
)
expect(
await signMessage(walletClient!, {
account: accounts[0].address,
message: {
raw: Uint8Array.from([
104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100,
]),
},
}),
).toMatchInlineSnapshot(
'"0xa461f509887bd19e312c0c58467ce8ff8e300d3c1a90b608a760c5b80318eaf15fe57c96f9175d6cd4daad4663763baa7e78836e067d0163e9a2ccf2ff753f5b1b"',
)
})

test('inferred account', async () => {
expect(
await signMessage(walletClientWithAccount!, {
Expand Down
15 changes: 11 additions & 4 deletions src/actions/wallet/signMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ import type { Transport } from '../../clients/transports/createTransport.js'
import { AccountNotFoundError } from '../../errors/account.js'
import type { GetAccountParameter } from '../../types/account.js'
import type { Chain } from '../../types/chain.js'
import type { Hex } from '../../types/misc.js'
import { toHex } from '../../utils/encoding/toHex.js'
import type { Hex, SignableMessage } from '../../types/misc.js'
import { stringToHex, toHex } from '../../utils/encoding/toHex.js'

export type SignMessageParameters<
TAccount extends Account | undefined = Account | undefined,
> = GetAccountParameter<TAccount> & {
message: string
message: SignableMessage
}

export type SignMessageReturnType = Hex
Expand Down Expand Up @@ -78,8 +78,15 @@ export async function signMessage<
})
const account = parseAccount(account_)
if (account.type === 'local') return account.signMessage({ message })

const message_ = (() => {
if (typeof message === 'string') return stringToHex(message)
if (message.raw instanceof Uint8Array) return toHex(message.raw)
return message.raw
})()

return client.request({
method: 'personal_sign',
params: [toHex(message), account.address],
params: [message_, account.address],
})
}
9 changes: 7 additions & 2 deletions src/adapters/ethers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { Address } from 'abitype'

import { toAccount } from '../accounts/toAccount.js'
import type { Hash } from '../types/misc.js'
import { toBytes } from '../utils/encoding/toBytes.js'
import { stringToBytes, toBytes } from '../utils/encoding/toBytes.js'

type BigNumberish = string | number | bigint
type BytesLike = string | Uint8Array
Expand Down Expand Up @@ -46,7 +46,12 @@ export const ethersWalletToAccount = (wallet: EthersWallet) =>
toAccount({
address: wallet.address as Address,
async signMessage({ message }) {
return (await wallet.signMessage(toBytes(message))) as Hash
const messageBytes = (() => {
if (typeof message === 'string') return stringToBytes(message)
if (message.raw instanceof Uint8Array) return message.raw
return toBytes(message.raw)
})()
return (await wallet.signMessage(messageBytes)) as Hash
},
async signTransaction(txn) {
// ethers type mappings
Expand Down
6 changes: 6 additions & 0 deletions src/types/misc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@ export type ByteArray = Uint8Array
export type Hex = `0x${string}`
export type Hash = `0x${string}`
export type LogTopic = Hex | Hex[] | null
export type SignableMessage =
| string
| {
/** Raw data representation of the message. */
raw: Hex | ByteArray
}
export type Signature = {
r: Hex
s: Hex
Expand Down
Loading

1 comment on commit 06ee89c

@vercel
Copy link

@vercel vercel bot commented on 06ee89c Jun 1, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.