- Consolidated duplicate UndoManagers to single instance - Fixed connection promise to only resolve on 'connected' status - Fixed WebSocketProvider import (WebsocketProvider) - Added proper doc.destroy() cleanup - Renamed isPresenceInitialized property to avoid conflict Co-Authored-By: Paperclip <noreply@paperclip.ing>
403 lines
12 KiB
TypeScript
403 lines
12 KiB
TypeScript
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
|
|
/** Chooses which verification path to try first before falling back. */
|
|
mode?: 'auto' | 'eoa' | (string & {}) | 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,
|
|
mode = 'auto',
|
|
} = 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 (mode === 'eoa') {
|
|
try {
|
|
const verified = isAddressEqual(
|
|
getAddress(address),
|
|
await recoverAddress({ hash, signature }),
|
|
)
|
|
if (verified) return true
|
|
} catch {}
|
|
}
|
|
|
|
if (SignatureErc8010.validate(signature))
|
|
return await verifyErc8010(client, {
|
|
...parameters,
|
|
multicallAddress,
|
|
signature,
|
|
})
|
|
return await verifyErc6492(client, {
|
|
...parameters,
|
|
verifierAddress,
|
|
signature,
|
|
})
|
|
} catch (error) {
|
|
if (mode !== 'eoa') {
|
|
// 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 {}
|