UNPKG

viem

Version:

TypeScript Interface for Ethereum

389 lines (355 loc) • 11.5 kB
import type { Address } from 'abitype' import { SignatureErc6492 } from 'ox/erc6492' import { SignatureErc8010 } from 'ox/erc8010' import type { Client } from '../../clients/createClient.js' import type { Transport } from '../../clients/transports/createTransport.js' import { erc1271Abi, erc6492SignatureValidatorAbi, multicall3Abi, } from '../../constants/abis.js' import { erc6492SignatureValidatorByteCode, multicall3Bytecode, } from '../../constants/contracts.js' import { CallExecutionError, ContractFunctionExecutionError, } from '../../errors/contract.js' import type { InvalidHexBooleanError } from '../../errors/encoding.js' import type { ErrorType } from '../../errors/utils.js' import type { Chain } from '../../types/chain.js' import type { ByteArray, Hex, Signature } from '../../types/misc.js' import type { OneOf } from '../../types/utils.js' import { type EncodeDeployDataErrorType, encodeDeployData, } from '../../utils/abi/encodeDeployData.js' import { type EncodeFunctionDataErrorType, encodeFunctionData, } from '../../utils/abi/encodeFunctionData.js' import { type GetAddressErrorType, getAddress, } from '../../utils/address/getAddress.js' import { type IsAddressEqualErrorType, isAddressEqual, } from '../../utils/address/isAddressEqual.js' import { verifyAuthorization } from '../../utils/authorization/verifyAuthorization.js' import { type ConcatHexErrorType, concatHex } from '../../utils/data/concat.js' import { type IsHexErrorType, isHex } from '../../utils/data/isHex.js' import { hexToBool } from '../../utils/encoding/fromHex.js' import { type BytesToHexErrorType, bytesToHex, type NumberToHexErrorType, numberToHex, } from '../../utils/encoding/toHex.js' import { getAction } from '../../utils/getAction.js' import { type RecoverAddressErrorType, recoverAddress, } from '../../utils/signature/recoverAddress.js' import { type SerializeSignatureErrorType, serializeSignature, } from '../../utils/signature/serializeSignature.js' import { type CallErrorType, type CallParameters, call } from './call.js' import { type GetCodeErrorType, getCode } from './getCode.js' import { type ReadContractErrorType, readContract } from './readContract.js' export type VerifyHashParameters = Pick< CallParameters, 'blockNumber' | 'blockTag' > & { /** The address that signed the original message. */ address: Address /** The chain to use. */ chain?: Chain | null | undefined /** The address of the ERC-6492 signature verifier contract. */ erc6492VerifierAddress?: Address | undefined /** The hash to be verified. */ hash: Hex /** Multicall3 address for ERC-8010 verification. */ multicallAddress?: Address | undefined /** The signature that was generated by signing the message with the address's private key. */ signature: Hex | ByteArray | Signature /** @deprecated use `erc6492VerifierAddress` instead. */ universalSignatureVerifierAddress?: Address | undefined } & OneOf<{ factory: Address; factoryData: Hex } | {}> export type VerifyHashReturnType = boolean export type VerifyHashErrorType = | BytesToHexErrorType | CallErrorType | ConcatHexErrorType | EncodeDeployDataErrorType | EncodeFunctionDataErrorType | ErrorType | GetAddressErrorType | GetCodeErrorType | InvalidHexBooleanError | IsAddressEqualErrorType | IsHexErrorType | NumberToHexErrorType | ReadContractErrorType | RecoverAddressErrorType | SerializeSignatureErrorType /** * Verifies a message hash onchain using ERC-6492. * * @param client - Client to use. * @param parameters - {@link VerifyHashParameters} * @returns Whether or not the signature is valid. {@link VerifyHashReturnType} */ export async function verifyHash<chain extends Chain | undefined>( client: Client<Transport, chain>, parameters: VerifyHashParameters, ): Promise<VerifyHashReturnType> { const { address, chain = client.chain, hash, erc6492VerifierAddress: verifierAddress = parameters.universalSignatureVerifierAddress ?? chain?.contracts?.erc6492Verifier?.address, multicallAddress = parameters.multicallAddress ?? chain?.contracts?.multicall3?.address, } = parameters if (chain?.verifyHash) return await chain.verifyHash(client, parameters) const signature = (() => { const signature = parameters.signature if (isHex(signature)) return signature if (typeof signature === 'object' && 'r' in signature && 's' in signature) return serializeSignature(signature) return bytesToHex(signature) })() try { if (SignatureErc8010.validate(signature)) return await verifyErc8010(client, { ...parameters, multicallAddress, signature, }) return await verifyErc6492(client, { ...parameters, verifierAddress, signature, }) } catch (error) { // Fallback attempt to verify the signature via ECDSA recovery. try { const verified = isAddressEqual( getAddress(address), await recoverAddress({ hash, signature }), ) if (verified) return true } catch {} if (error instanceof VerificationError) { // if the execution fails, the signature was not valid and an internal method inside of the validator reverted // this can happen for many reasons, for example if signer can not be recovered from the signature // or if the signature has no valid format return false } throw error } } /** @internal */ export async function verifyErc8010( client: Client, parameters: verifyErc8010.Parameters, ) { const { address, blockNumber, blockTag, hash, multicallAddress } = parameters const { authorization: authorization_ox, data: initData, signature, to, } = SignatureErc8010.unwrap(parameters.signature) // Check if already delegated const code = await getCode(client, { address, blockNumber, blockTag, } as never) // If already delegated, perform standard ERC-1271 verification. if (code === concatHex(['0xef0100', authorization_ox.address])) return await verifyErc1271(client, { address, blockNumber, blockTag, hash, signature, }) const authorization = { address: authorization_ox.address, chainId: Number(authorization_ox.chainId), nonce: Number(authorization_ox.nonce), r: numberToHex(authorization_ox.r, { size: 32 }), s: numberToHex(authorization_ox.s, { size: 32 }), yParity: authorization_ox.yParity, } as const const valid = await verifyAuthorization({ address, authorization, }) if (!valid) throw new VerificationError() // Deployless verification. const results = await getAction( client, readContract, 'readContract', )({ ...(multicallAddress ? { address: multicallAddress } : { code: multicall3Bytecode }), authorizationList: [authorization], abi: multicall3Abi, blockNumber, blockTag: 'pending', functionName: 'aggregate3', args: [ [ ...(initData ? ([ { allowFailure: true, target: to ?? address, callData: initData, }, ] as const) : []), { allowFailure: true, target: address, callData: encodeFunctionData({ abi: erc1271Abi, functionName: 'isValidSignature', args: [hash, signature], }), }, ], ], }) const data = results[results.length - 1]?.returnData if (data?.startsWith('0x1626ba7e')) return true throw new VerificationError() } export namespace verifyErc8010 { export type Parameters = Pick<CallParameters, 'blockNumber' | 'blockTag'> & { /** The address that signed the original message. */ address: Address /** The hash to be verified. */ hash: Hex /** Multicall3 address for ERC-8010 verification. */ multicallAddress?: Address | undefined /** The signature that was generated by signing the message with the address's private key. */ signature: Hex } } /** @internal */ // biome-ignore lint/correctness/noUnusedVariables: _ async function verifyErc6492( client: Client, parameters: verifyErc6492.Parameters, ) { const { address, factory, factoryData, hash, signature, verifierAddress, ...rest } = parameters const wrappedSignature = await (async () => { // If no `factory` or `factoryData` is provided, it is assumed that the // address is not a Smart Account, or the Smart Account is already deployed. if (!factory && !factoryData) return signature // If the signature is already wrapped, return the signature. if (SignatureErc6492.validate(signature)) return signature // If the Smart Account is not deployed, wrap the signature with a 6492 wrapper // to perform counterfactual validation. return SignatureErc6492.wrap({ data: factoryData!, signature, to: factory!, }) })() const args = verifierAddress ? ({ to: verifierAddress, data: encodeFunctionData({ abi: erc6492SignatureValidatorAbi, functionName: 'isValidSig', args: [address, hash, wrappedSignature], }), ...rest, } as unknown as CallParameters) : ({ data: encodeDeployData({ abi: erc6492SignatureValidatorAbi, args: [address, hash, wrappedSignature], bytecode: erc6492SignatureValidatorByteCode, }), ...rest, } as unknown as CallParameters) const { data } = await getAction( client, call, 'call', )(args).catch((error) => { if (error instanceof CallExecutionError) throw new VerificationError() throw error }) if (hexToBool(data ?? '0x0')) return true throw new VerificationError() } export namespace verifyErc6492 { export type Parameters = Pick<CallParameters, 'blockNumber' | 'blockTag'> & { /** The address that signed the original message. */ address: Address /** The hash to be verified. */ hash: Hex /** The signature that was generated by signing the message with the address's private key. */ signature: Hex /** The address of the ERC-6492 signature verifier contract. */ verifierAddress?: Address | undefined } & OneOf<{ factory: Address; factoryData: Hex } | {}> } /** @internal */ export async function verifyErc1271( client: Client, parameters: verifyErc1271.Parameters, ) { const { address, blockNumber, blockTag, hash, signature } = parameters const result = await getAction( client, readContract, 'readContract', )({ address, abi: erc1271Abi, args: [hash, signature], blockNumber, blockTag, functionName: 'isValidSignature', }).catch((error) => { if (error instanceof ContractFunctionExecutionError) throw new VerificationError() throw error }) if (result.startsWith('0x1626ba7e')) return true throw new VerificationError() } export namespace verifyErc1271 { export type Parameters = Pick<CallParameters, 'blockNumber' | 'blockTag'> & { /** The address that signed the original message. */ address: Address /** The hash to be verified. */ hash: Hex /** The signature that was generated by signing the message with the address's private key. */ signature: Hex } } class VerificationError extends Error {}