Skip to content

Commit

Permalink
feat: accept Signature as a valid type for signature parameters
Browse files Browse the repository at this point in the history
  • Loading branch information
jxom committed Apr 24, 2024
1 parent 735726c commit 5e8a249
Show file tree
Hide file tree
Showing 26 changed files with 284 additions and 50 deletions.
5 changes: 5 additions & 0 deletions .changeset/rotten-hounds-press.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"viem": patch
---

Added `Signature` as a valid input type to `signature` parameters.
2 changes: 1 addition & 1 deletion site/pages/docs/actions/public/verifyMessage.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ const valid = await publicClient.verifyMessage({

### signature

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

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

Expand Down
2 changes: 1 addition & 1 deletion site/pages/docs/actions/public/verifyTypedData.md
Original file line number Diff line number Diff line change
Expand Up @@ -324,7 +324,7 @@ const valid = await publicClient.verifyTypedData({

### signature

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

The signature of the typed data.

Expand Down
2 changes: 1 addition & 1 deletion site/pages/docs/utilities/recoverAddress.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ const address = await recoverAddress({

### signature

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

The signature of the hash.

Expand Down
2 changes: 1 addition & 1 deletion site/pages/docs/utilities/recoverMessageAddress.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ const address = await recoverMessageAddress({

### signature

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

The signature of the message.

Expand Down
2 changes: 1 addition & 1 deletion site/pages/docs/utilities/recoverPublicKey.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ const publicKey = await recoverPublicKey({

### signature

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

The signature of the hash.

Expand Down
2 changes: 1 addition & 1 deletion site/pages/docs/utilities/verifyMessage.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ const valid = await verifyMessage({

### signature

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

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

Expand Down
2 changes: 1 addition & 1 deletion site/pages/docs/utilities/verifyTypedData.md
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,7 @@ const valid = await verifyTypedData({

### signature

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

The signature of the typed data.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ const address = await recoverAuthMessageAddress({

### signature

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

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

Expand Down
19 changes: 19 additions & 0 deletions src/actions/public/verifyHash.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { anvilMainnet } from '../../../test/src/anvil.js'

import type { Hex } from '../../types/misc.js'
import { hashMessage, toBytes } from '../../utils/index.js'
import { hexToSignature } from '../../utils/signature/hexToSignature.js'
import { verifyHash } from './verifyHash.js'

const client = anvilMainnet.getClient()
Expand All @@ -20,6 +21,15 @@ describe('verifyHash', async () => {
'0xefd5fb29a274ea6682673d8b3caa9263e936d48d486e5df68893003e0a76496439594d12245008c6fba1c8e3ef28241cffe1bef27ff6bca487b167f261f329251c',
expectedResult: true,
},
{
_name: 'deployed, supports ERC1271, valid signature, plaintext',
address: smartAccountConfig.address,
hash: hashMessage('This is a test message for viem!'),
signature: hexToSignature(
'0xefd5fb29a274ea6682673d8b3caa9263e936d48d486e5df68893003e0a76496439594d12245008c6fba1c8e3ef28241cffe1bef27ff6bca487b167f261f329251c',
),
expectedResult: true,
},
{
_name: 'deployed, supports ERC1271, invalid signature',
address: smartAccountConfig.address,
Expand All @@ -42,6 +52,15 @@ describe('verifyHash', async () => {
'0xa461f509887bd19e312c0c58467ce8ff8e300d3c1a90b608a760c5b80318eaf15fe57c96f9175d6cd4daad4663763baa7e78836e067d0163e9a2ccf2ff753f5b1b',
expectedResult: true,
},
{
_name: 'undeployed, with correct signature',
address: accounts[0].address,
hash: hashMessage('hello world'),
signature: hexToSignature(
'0xa461f509887bd19e312c0c58467ce8ff8e300d3c1a90b608a760c5b80318eaf15fe57c96f9175d6cd4daad4663763baa7e78836e067d0163e9a2ccf2ff753f5b1b',
),
expectedResult: true,
},
{
_name: 'undeployed, with wrong signature',
address: address.notDeployed,
Expand Down
22 changes: 15 additions & 7 deletions src/actions/public/verifyHash.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,25 @@
import type { Address } from 'abitype'

import { signatureToHex } from '../../accounts/index.js'
import type { Client } from '../../clients/createClient.js'
import type { Transport } from '../../clients/transports/createTransport.js'
import { universalSignatureValidatorAbi } from '../../constants/abis.js'
import { universalSignatureValidatorByteCode } from '../../constants/contracts.js'
import { CallExecutionError } from '../../errors/contract.js'
import type { ErrorType } from '../../errors/utils.js'
import type { Chain } from '../../types/chain.js'
import type { ByteArray, Hex } from '../../types/misc.js'
import type { EncodeDeployDataErrorType } from '../../utils/abi/encodeDeployData.js'
import type { ByteArray, Hex, Signature } from '../../types/misc.js'
import {
type EncodeDeployDataErrorType,
encodeDeployData,
} from '../../utils/abi/encodeDeployData.js'
import {
type IsBytesEqualErrorType,
isBytesEqual,
} from '../../utils/data/isBytesEqual.js'
import type { IsHexErrorType } from '../../utils/data/isHex.js'
import type { ToHexErrorType } from '../../utils/encoding/toHex.js'
import { type IsHexErrorType, isHex } from '../../utils/data/isHex.js'
import { type ToHexErrorType, bytesToHex } from '../../utils/encoding/toHex.js'
import { getAction } from '../../utils/getAction.js'
import { encodeDeployData, isHex, toHex } from '../../utils/index.js'
import { type CallErrorType, type CallParameters, call } from './call.js'

export type VerifyHashParameters = Pick<
Expand All @@ -28,7 +31,7 @@ export type VerifyHashParameters = Pick<
/** The hash to be verified. */
hash: Hex
/** The signature that was generated by signing the message with the address's private key. */
signature: Hex | ByteArray
signature: Hex | ByteArray | Signature
}

export type VerifyHashReturnType = boolean
Expand All @@ -52,7 +55,12 @@ export async function verifyHash<TChain extends Chain | undefined>(
client: Client<Transport, TChain>,
{ address, hash, signature, ...callRequest }: VerifyHashParameters,
): Promise<VerifyHashReturnType> {
const signatureHex = isHex(signature) ? signature : toHex(signature)
const signatureHex = (() => {
if (isHex(signature)) return signature
if (typeof signature === 'object' && 'r' in signature && 's' in signature)
return signatureToHex(signature)
return bytesToHex(signature)
})()

try {
const { data } = await getAction(
Expand Down
9 changes: 7 additions & 2 deletions src/actions/public/verifyMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,12 @@ import type { Client } from '../../clients/createClient.js'
import type { Transport } from '../../clients/transports/createTransport.js'
import type { ErrorType } from '../../errors/utils.js'
import type { Chain } from '../../types/chain.js'
import type { ByteArray, Hex, SignableMessage } from '../../types/misc.js'
import type {
ByteArray,
Hex,
SignableMessage,
Signature,
} from '../../types/misc.js'
import { hashMessage } from '../../utils/index.js'
import type { HashMessageErrorType } from '../../utils/signature/hashMessage.js'
import {
Expand All @@ -19,7 +24,7 @@ export type VerifyMessageParameters = Omit<VerifyHashParameters, 'hash'> & {
/** The message to be verified. */
message: SignableMessage
/** The signature that was generated by signing the message with the address's private key. */
signature: Hex | ByteArray
signature: Hex | ByteArray | Signature
}

export type VerifyMessageReturnType = boolean
Expand Down
4 changes: 2 additions & 2 deletions src/actions/public/verifyTypedData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import type { Client } from '../../clients/createClient.js'
import type { Transport } from '../../clients/transports/createTransport.js'
import type { ErrorType } from '../../errors/utils.js'
import type { Chain } from '../../types/chain.js'
import type { ByteArray, Hex } from '../../types/misc.js'
import type { ByteArray, Hex, Signature } from '../../types/misc.js'
import type { TypedDataDefinition } from '../../types/typedData.js'
import {
type HashTypedDataErrorType,
Expand All @@ -24,7 +24,7 @@ export type VerifyTypedDataParameters<
/** The address to verify the typed data for. */
address: Address
/** The signature to verify */
signature: Hex | ByteArray
signature: Hex | ByteArray | Signature
}

export type VerifyTypedDataReturnType = boolean
Expand Down
4 changes: 2 additions & 2 deletions src/experimental/eip3074/utils/recoverAuthMessageAddress.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { Address } from 'abitype'

import type { ByteArray, Hex } from '../../../types/misc.js'
import type { ByteArray, Hex, Signature } from '../../../types/misc.js'

import type { ErrorType } from '../../../errors/utils.js'
import { keccak256 } from '../../../utils/hash/keccak256.js'
Expand All @@ -11,7 +11,7 @@ import {
import { type ToAuthMessageParameters, toAuthMessage } from './toAuthMessage.js'

export type RecoverAuthMessageAddressParameters = ToAuthMessageParameters & {
signature: Hex | ByteArray
signature: Hex | ByteArray | Signature
}

export type RecoverAuthMessageAddressReturnType = Address
Expand Down
38 changes: 38 additions & 0 deletions src/utils/signature/hexToSignature.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,42 @@ test('default', () => {
v: 27n,
yParity: 0,
})

expect(
hexToSignature(
'0xa461f509887bd19e312c0c58467ce8ff8e300d3c1a90b608a760c5b80318eaf15fe57c96f9175d6cd4daad4663763baa7e78836e067d0163e9a2ccf2ff753f5b00',
),
).toEqual({
r: '0xa461f509887bd19e312c0c58467ce8ff8e300d3c1a90b608a760c5b80318eaf1',
s: '0x5fe57c96f9175d6cd4daad4663763baa7e78836e067d0163e9a2ccf2ff753f5b',
yParity: 0,
})

expect(
hexToSignature(
'0xc4d8bcda762d35ea79d9542b23200f46c2c1899db15bf929bbacaf609581db0831538374a01206517edd934e474212a0f1e2d62e9a01cd64f1cf94ea2e09884901',
),
).toEqual({
r: '0xc4d8bcda762d35ea79d9542b23200f46c2c1899db15bf929bbacaf609581db08',
s: '0x31538374a01206517edd934e474212a0f1e2d62e9a01cd64f1cf94ea2e098849',
yParity: 1,
})
})

test('invalid yParityOrV value', async () => {
expect(() =>
hexToSignature(
'0x6e100a352ec6ad1b70802290e18aeed190704973570f3b8ed42cb9808e2ea6bf4a90a229a244495b41890987806fcbd2d5d23fc0dbe5f5256c2613c039d76db81d',
),
).toThrowErrorMatchingInlineSnapshot('[Error: Invalid yParityOrV value]')
expect(() =>
hexToSignature(
'0x6e100a352ec6ad1b70802290e18aeed190704973570f3b8ed42cb9808e2ea6bf4a90a229a244495b41890987806fcbd2d5d23fc0dbe5f5256c2613c039d76db802',
),
).toThrowErrorMatchingInlineSnapshot('[Error: Invalid yParityOrV value]')
expect(() =>
hexToSignature(
'0x6e100a352ec6ad1b70802290e18aeed190704973570f3b8ed42cb9808e2ea6bf4a90a229a244495b41890987806fcbd2d5d23fc0dbe5f5256c2613c039d76db81a',
),
).toThrowErrorMatchingInlineSnapshot('[Error: Invalid yParityOrV value]')
})
19 changes: 16 additions & 3 deletions src/utils/signature/hexToSignature.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,24 @@ export type HexToSignatureErrorType = NumberToHexErrorType | ErrorType
*/
export function hexToSignature(signatureHex: Hex) {
const { r, s } = secp256k1.Signature.fromCompact(signatureHex.slice(2, 130))
const v = BigInt(`0x${signatureHex.slice(130)}`)
const yParityOrV = Number(`0x${signatureHex.slice(130)}`)
const [v, yParity] = (() => {
if (yParityOrV === 0 || yParityOrV === 1) return [undefined, yParityOrV]
if (yParityOrV === 27) return [BigInt(yParityOrV), 0]
if (yParityOrV === 28) return [BigInt(yParityOrV), 1]
throw new Error('Invalid yParityOrV value')
})()

if (typeof v !== 'undefined')
return {
r: numberToHex(r, { size: 32 }),
s: numberToHex(s, { size: 32 }),
v,
yParity,
} satisfies Signature
return {
r: numberToHex(r, { size: 32 }),
s: numberToHex(s, { size: 32 }),
v,
yParity: v === 28n ? 1 : 0,
yParity,
} satisfies Signature
}
37 changes: 37 additions & 0 deletions src/utils/signature/recoverAddress.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { getAddress } from '../address/getAddress.js'
import { toBytes } from '../encoding/toBytes.js'

import { hashMessage } from './hashMessage.js'
import { hexToSignature } from './hexToSignature.js'
import { recoverAddress } from './recoverAddress.js'

test('default', async () => {
Expand All @@ -16,6 +17,15 @@ test('default', async () => {
}),
).toEqual(getAddress(accounts[0].address))

expect(
await recoverAddress({
hash: hashMessage('hello world'),
signature: hexToSignature(
'0xa461f509887bd19e312c0c58467ce8ff8e300d3c1a90b608a760c5b80318eaf15fe57c96f9175d6cd4daad4663763baa7e78836e067d0163e9a2ccf2ff753f5b1b',
),
}),
).toEqual(getAddress(accounts[0].address))

expect(
await recoverAddress({
hash: hashMessage('🥵'),
Expand All @@ -24,6 +34,15 @@ test('default', async () => {
}),
).toEqual(getAddress(accounts[0].address))

expect(
await recoverAddress({
hash: hashMessage('🥵'),
signature: hexToSignature(
'0x05c99bbbe9fac3ad61721a815d19d6771ad39f3e8dffa7ae7561358f20431d8e7f9e1d487c77355790c79c6eb0b0d63690f690615ef99ee3e4f25eef0317d0701b',
),
}),
).toEqual(getAddress(accounts[0].address))

expect(
await recoverAddress({
hash: hashMessage('hello world', 'bytes'),
Expand All @@ -32,6 +51,15 @@ test('default', async () => {
}),
).toEqual(getAddress(accounts[0].address))

expect(
await recoverAddress({
hash: hashMessage('hello world', 'bytes'),
signature: hexToSignature(
'0xa461f509887bd19e312c0c58467ce8ff8e300d3c1a90b608a760c5b80318eaf15fe57c96f9175d6cd4daad4663763baa7e78836e067d0163e9a2ccf2ff753f5b1b',
),
}),
).toEqual(getAddress(accounts[0].address))

expect(
await recoverAddress({
hash: hashMessage('🥵', 'bytes'),
Expand All @@ -40,6 +68,15 @@ test('default', async () => {
}),
).toEqual(getAddress(accounts[0].address))

expect(
await recoverAddress({
hash: hashMessage('🥵', 'bytes'),
signature: hexToSignature(
'0x05c99bbbe9fac3ad61721a815d19d6771ad39f3e8dffa7ae7561358f20431d8e7f9e1d487c77355790c79c6eb0b0d63690f690615ef99ee3e4f25eef0317d0701b',
),
}),
).toEqual(getAddress(accounts[0].address))

expect(
await recoverAddress({
hash: hashMessage('hello world', 'bytes'),
Expand Down
4 changes: 2 additions & 2 deletions src/utils/signature/recoverAddress.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import type { Address } from 'abitype'

import { publicKeyToAddress } from '../../accounts/utils/publicKeyToAddress.js'
import type { ByteArray, Hex } from '../../types/misc.js'
import type { ByteArray, Hex, Signature } from '../../types/misc.js'

import type { ErrorType } from '../../errors/utils.js'
import { recoverPublicKey } from './recoverPublicKey.js'

export type RecoverAddressParameters = {
hash: Hex | ByteArray
signature: Hex | ByteArray
signature: Hex | ByteArray | Signature
}

export type RecoverAddressReturnType = Address
Expand Down
Loading

0 comments on commit 5e8a249

Please sign in to comment.