Checking Signatures

Validating custom verification logic in contract accounts.

Smart accounts can have arbitrary verification logic. This is an advantage over an EOAs which limits users to only ECDSA, but requires applications to check the custom verification logic.


Example Code

The best practice is to use ERC-6492 to verify the signature smart account, even if it is not yet deployed. This can be done either on-chain or off-chain.

For convenience, the logic for ERC-6492 can be called from a smart contract called UniversalSigValidator. You can check this off-chain using its bytecode like in the example below, or even check in on-chain by calling the contract's isValidSig function.

// Off-chain signature validation using ethers.js
// Replace `ethers.verifyMessage(message, signature) === signer` with this:

// Bytecode of the UniversalSigValidator's isValidSig function for verifying off-chain
// Source: https://github.com/AmbireTech/signature-validator/blob/dabe1d3a8adac2cf6df0b41d825f8b7b8dae2d34/dist/index.js#L45
const validateSigOffchainBytecode= "0x60806040523480156200001157600080fd5b50604051620007003803806200070083398101604081905262000034916200056f565b6000620000438484846200004f565b9050806000526001601ff35b600080846001600160a01b0316803b806020016040519081016040528181526000908060200190933c90507f6492649264926492649264926492649264926492649264926492649264926492620000a68462000451565b036200021f57600060608085806020019051810190620000c79190620005ce565b8651929550909350915060000362000192576000836001600160a01b031683604051620000f5919062000643565b6000604051808303816000865af19150503d806000811462000134576040519150601f19603f3d011682016040523d82523d6000602084013e62000139565b606091505b5050905080620001905760405162461bcd60e51b815260206004820152601e60248201527f5369676e617475726556616c696461746f723a206465706c6f796d656e74000060448201526064015b60405180910390fd5b505b604051630b135d3f60e11b808252906001600160a01b038a1690631626ba7e90620001c4908b90869060040162000661565b602060405180830381865afa158015620001e2573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906200020891906200069d565b6001600160e01b031916149450505050506200044a565b805115620002b157604051630b135d3f60e11b808252906001600160a01b03871690631626ba7e9062000259908890889060040162000661565b602060405180830381865afa15801562000277573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906200029d91906200069d565b6001600160e01b031916149150506200044a565b8251604114620003195760405162461bcd60e51b815260206004820152603a6024820152600080516020620006e083398151915260448201527f3a20696e76616c6964207369676e6174757265206c656e677468000000000000606482015260840162000187565b620003236200046b565b506020830151604080850151855186939260009185919081106200034b576200034b620006c9565b016020015160f81c9050601b81148015906200036b57508060ff16601c14155b15620003cf5760405162461bcd60e51b815260206004820152603b6024820152600080516020620006e083398151915260448201527f3a20696e76616c6964207369676e617475726520762076616c75650000000000606482015260840162000187565b6040805160008152602081018083528a905260ff83169181019190915260608101849052608081018390526001600160a01b038a169060019060a0016020604051602081039080840390855afa1580156200042e573d6000803e3d6000fd5b505050602060405103516001600160a01b031614955050505050505b9392505050565b60006020825110156200046357600080fd5b508051015190565b60405180606001604052806003906020820280368337509192915050565b6001600160a01b03811681146200049f57600080fd5b50565b634e487b7160e01b600052604160045260246000fd5b60005b83811015620004d5578181015183820152602001620004bb565b50506000910152565b600082601f830112620004f057600080fd5b81516001600160401b03808211156200050d576200050d620004a2565b604051601f8301601f19908116603f01168101908282118183101715620005385762000538620004a2565b816040528381528660208588010111156200055257600080fd5b62000565846020830160208901620004b8565b9695505050505050565b6000806000606084860312156200058557600080fd5b8351620005928162000489565b6020850151604086015191945092506001600160401b03811115620005b657600080fd5b620005c486828701620004de565b9150509250925092565b600080600060608486031215620005e457600080fd5b8351620005f18162000489565b60208501519093506001600160401b03808211156200060f57600080fd5b6200061d87838801620004de565b935060408601519150808211156200063457600080fd5b50620005c486828701620004de565b6000825162000657818460208701620004b8565b9190910192915050565b828152604060208201526000825180604084015262000688816060850160208701620004b8565b601f01601f1916919091016060019392505050565b600060208284031215620006b057600080fd5b81516001600160e01b0319811681146200044a57600080fd5b634e487b7160e01b600052603260045260246000fdfe5369676e617475726556616c696461746f72237265636f7665725369676e6572"

const isValidSignature = '0x01' === await provider.call({
  data: ethers.utils.concat([
    validateSigOffchainBytecode,
    (new ethers.utils.AbiCoder()).encode(['address', 'bytes32', 'bytes'], [signer, hash, signature])
  ])
})
// Off-chain signature validation using @ambire/signature-validator npm library
// Replace `ethers.verifyMessage(message, signature) === signer` with this:

import ethers from 'ethers';
import { verifyMessage } from '@ambire/signature-validator';

const provider = new ethers.providers.JsonRpcProvider('https://polygon-rpc.com')

async function run() {
	// Replace `ethers.verifyMessage(message, signature) === signer` with this:
	const isValidSig = await verifyMessage({
	    signer: '0xaC39b311DCEb2A4b2f5d8461c1cdaF756F4F7Ae9',
	    message: 'My funds are SAFU with Ambire Wallet',
	    signature: '0x9863d84f3119ac01d9e3bf9294e6c0c3572a07780fc7c49e8dc913806f4b1dbd4cc075462dc84422a9b981b2556f9c9197d76da7ba3603e53e9300869c574d821c',
	    provider,
	})
	console.log('is the sig valid: ', isValidSig)
}
run().catch(e => console.error(e))
// On-chain verification of a signature using the ERC-6492 UniversalSigValidator contract.

UniversalSigValidator.isValidSig(_signer, _hash, _signature);

ERC-1271 and ERC-6492

Validating smart contract signatures is historically done using ERC-1271.

ERC-1271 defines an interface, isValidSignature, which can be called by an application to validate a signature. In this method an app checks if the user's address has code deployed, and if so, calls isValidSignature.

One limitation of ERC-1271 is that this method requires the smart account contract to already be deployed on chain. The best user experience is often for smart accounts to defer contract deployment until the first user action.

ERC-6492 extends ERC-1271 to allow any contract or off-chain actor to verify a signature on behalf of a contract that is not deployed. This can be done because ERC-4337 accounts are counterfactual.

We recommend using ERC-6492.

How to implement

ERC-6492 is more complex than ERC-1271, so we recommend using an SDK that already has the logic built in.

Creating signatures

The smart account will still implement ERC-1271 in its code, but its signatures may need to be wrapped to handle if it isn't yet deployed.

  • If the contract is deployed, follow ERC-1271 to produce a signature that can be checked with the contract's isValidSignature function
  • If the contract is not deployed yet, wrap the signature as defined in ERC-6492.

There is an additional edge case, where the contract is deployed but is not ready to verify with ERC-1271. ERC-6492 handles this by requiring the signature to be wrapped with the necessary transaction to make the contract ready to verify using ERC-1271.

Validating signatures

To aid in the verification of smart account signatures, ERC-6492 includes a singleton contract that can validate ERC-6492 signatures. This singleton contract is called UniversalSigValidator.

On-chain validation

To verify signatures on-chain, the ERC-6492 singleton contract has an isValidSig function that can be called. It accepts a signer, hash, and signature and returns a boolean of whether the signature is valid or not. This method may revert if the underlying calls revert.

UniversalSigValidator.isValidSig(_signer, _hash, _signature)

Off-chain validation

The ERC-6492 contract has a ValidateSigOffchain helper function that allows you to validate a signature in one eth_call without deploying the smart account.

Like its on-chain counterpart isValidSig, ValidateSigOffchain accepts a signer, hash, and signature.

Here is an example of how the ethers.js library can be used to make this eth_call and return whether the signature is valid.

const isValidSignature = '0x01' === await provider.call({
  data: ethers.utils.concat([
    validateSigOffchainBytecode,
    (new ethers.utils.AbiCoder()).encode(['address', 'bytes32', 'bytes'], [signer, hash, signature])
  ])
})