Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add ability to call deployed universal signature validator for chains that do not support createless call #2807

Merged
merged 6 commits into from
Oct 13, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 86 additions & 0 deletions contracts/src/UniversalSigValidator.sol
coffeexcoin marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
pragma solidity ^0.8.17;

// SPDX-License-Identifier: UNLICENSED

// https://github.com/AmbireTech/signature-validator

interface IERC1271Wallet {
function isValidSignature(
bytes32 hash,
bytes calldata signature
) external view returns (bytes4 magicValue);
}

contract VerifySig {
// ERC-6492 suffix: https://eips.ethereum.org/EIPS/eip-6492
bytes32 private constant ERC6492_DETECTION_SUFFIX =
0x6492649264926492649264926492649264926492649264926492649264926492;
bytes4 private constant ERC1271_SUCCESS = 0x1626ba7e;

/**
* @notice Verifies that the signature is valid for that signer and hash
*/
function isValidSig(
address _signer,
bytes32 _hash,
bytes memory _signature
) public returns (bool) {
// The order here is strictly defined in https://eips.ethereum.org/EIPS/eip-6492
// - ERC-6492 suffix check and verification first, while being permissive in case the contract is already deployed so as to not invalidate old sigs
// - ERC-1271 verification if there's contract code
// - finally, ecrecover
if (trailingBytes32(_signature) == ERC6492_DETECTION_SUFFIX) {
address create2Factory;
bytes memory factoryCalldata;
bytes memory originalSig;
(create2Factory, factoryCalldata, originalSig) = abi.decode(
_signature,
(address, bytes, bytes)
);

(bool success, ) = create2Factory.call(factoryCalldata);

if (_signer.code.length == 0) {
require(success, "SignatureValidator: deployment");
}

return
IERC1271Wallet(_signer).isValidSignature(_hash, originalSig) ==
ERC1271_SUCCESS;
}

if (_signer.code.length > 0) {
return
IERC1271Wallet(_signer).isValidSignature(_hash, _signature) ==
ERC1271_SUCCESS;
}

// ecrecover verification
require(
_signature.length == 65,
"SignatureValidator#recoverSigner: invalid signature length"
);
bytes32[3] memory _sig;
assembly {
_sig := _signature
}
bytes32 r = _sig[1];
bytes32 s = _sig[2];
uint8 v = uint8(_signature[64]);
if (v != 27 && v != 28) {
revert(
"SignatureValidator#recoverSigner: invalid signature v value"
);
}
return ecrecover(_hash, v, r, s) == _signer;
}

function trailingBytes32(
bytes memory data
) internal pure returns (bytes32 ret) {
require(data.length >= 32);
assembly {
ret := mload(add(data, mload(data)))
}
}
}
86 changes: 4 additions & 82 deletions contracts/src/deployless/DeploylessUniversalSigValidator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,92 +3,14 @@ pragma solidity ^0.8.17;
// SPDX-License-Identifier: UNLICENSED

// https://github.com/AmbireTech/signature-validator
import { VerifySig } from "../UniversalSigValidator.sol";

interface IERC1271Wallet {
function isValidSignature(
bytes32 hash,
bytes calldata signature
) external view returns (bytes4 magicValue);
}

contract VerifySig {
contract DeploylessVerifySig is VerifySig {
constructor(address _signer, bytes32 _hash, bytes memory _signature) {
bool isValidSig = isValidUniversalSig(_signer, _hash, _signature);
bool isValid = isValidSig(_signer, _hash, _signature);
assembly {
mstore(0, isValidSig)
mstore(0, isValid)
return(31, 1)
}
}

// ERC-6492 suffix: https://eips.ethereum.org/EIPS/eip-6492
bytes32 private constant ERC6492_DETECTION_SUFFIX =
0x6492649264926492649264926492649264926492649264926492649264926492;
bytes4 private constant ERC1271_SUCCESS = 0x1626ba7e;

/**
* @notice Verifies that the signature is valid for that signer and hash
*/
function isValidUniversalSig(
address _signer,
bytes32 _hash,
bytes memory _signature
) public returns (bool) {
// The order here is strictly defined in https://eips.ethereum.org/EIPS/eip-6492
// - ERC-6492 suffix check and verification first, while being permissive in case the contract is already deployed so as to not invalidate old sigs
// - ERC-1271 verification if there's contract code
// - finally, ecrecover
if (trailingBytes32(_signature) == ERC6492_DETECTION_SUFFIX) {
address create2Factory;
bytes memory factoryCalldata;
bytes memory originalSig;
(create2Factory, factoryCalldata, originalSig) = abi.decode(
_signature,
(address, bytes, bytes)
);

(bool success, ) = create2Factory.call(factoryCalldata);

if (_signer.code.length == 0) {
require(success, "SignatureValidator: deployment");
}

return
IERC1271Wallet(_signer).isValidSignature(_hash, originalSig) ==
ERC1271_SUCCESS;
}

if (_signer.code.length > 0) {
return
IERC1271Wallet(_signer).isValidSignature(_hash, _signature) ==
ERC1271_SUCCESS;
}

// ecrecover verification
require(
_signature.length == 65,
"SignatureValidator#recoverSigner: invalid signature length"
);
bytes32[3] memory _sig;
assembly {
_sig := _signature
}
bytes32 r = _sig[1];
bytes32 s = _sig[2];
uint8 v = uint8(_signature[64]);
if (v != 27 && v != 28) {
revert(
"SignatureValidator#recoverSigner: invalid signature v value"
);
}
return ecrecover(_hash, v, r, s) == _signer;
}

function trailingBytes32(
bytes memory data
) internal pure returns (bytes32 ret) {
require(data.length >= 32);
assembly {
ret := mload(add(data, mload(data)))
}
}
}
54 changes: 53 additions & 1 deletion src/actions/public/verifyHash.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ import {
import { ensPublicResolverConfig, smartAccountConfig } from '~test/src/abis.js'
import { anvilMainnet } from '~test/src/anvil.js'
import { accounts, address } from '~test/src/constants.js'
import { deploySoladyAccount_07 } from '~test/src/utils.js'
import {
deploySoladyAccount_07,
deployUniversalSignatureVerifier,
} from '~test/src/utils.js'
import {
entryPoint07Abi,
entryPoint07Address,
Expand Down Expand Up @@ -151,6 +154,55 @@ describe('smart account', async () => {
).resolves.toBe(true)
})

test('undeployed with predeployed verifier', async () => {
const { factoryAddress } = await deploySoladyAccount_07()
const { contractAddress: verifySig } =
await deployUniversalSignatureVerifier()

const overrideClient = {
...client,
chain: {
...client.chain,
contracts: {
...client.chain.contracts,
universalSignatureVerifier: { address: verifySig },
},
},
}

const { result: verifier } = await simulateContract(overrideClient, {
account: localAccount,
abi: SoladyAccountFactory07.abi,
address: factoryAddress,
functionName: 'createAccount',
args: [localAccount.address, pad('0x0')],
})

const factoryData = encodeFunctionData({
abi: SoladyAccountFactory07.abi,
functionName: 'createAccount',
args: [localAccount.address, pad('0x0')],
})

const signature = await signMessageErc1271(overrideClient, {
account: localAccount,
factory: factoryAddress,
factoryData,
message: 'hello world',
verifier,
})

expect(
verifyHash(overrideClient, {
address: verifier,
factory: factoryAddress,
factoryData,
hash: hashMessage('hello world'),
signature,
}),
).resolves.toBe(true)
})

test('deployed w/ factory + factoryData', async () => {
const { factoryAddress } = await deploySoladyAccount_07()

Expand Down
43 changes: 25 additions & 18 deletions src/actions/public/verifyHash.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ 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 { 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'
Expand All @@ -15,13 +16,10 @@ import {
} from '../../utils/abi/encodeDeployData.js'
import { getAddress } from '../../utils/address/getAddress.js'
import { isAddressEqual } from '../../utils/address/isAddressEqual.js'
import {
type IsBytesEqualErrorType,
isBytesEqual,
} from '../../utils/data/isBytesEqual.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 { encodeFunctionData, hexToBool } from '../../utils/index.js'
import { isErc6492Signature } from '../../utils/signature/isErc6492Signature.js'
import { recoverAddress } from '../../utils/signature/recoverAddress.js'
import { serializeErc6492Signature } from '../../utils/signature/serializeErc6492Signature.js'
Expand All @@ -46,7 +44,7 @@ export type VerifyHashErrorType =
| CallErrorType
| IsHexErrorType
| ToHexErrorType
| IsBytesEqualErrorType
| InvalidHexBooleanError
| EncodeDeployDataErrorType
| ErrorType

Expand Down Expand Up @@ -88,20 +86,29 @@ export async function verifyHash<chain extends Chain | undefined>(
})()

try {
const { data } = await getAction(
client,
call,
'call',
)({
data: encodeDeployData({
abi: universalSignatureValidatorAbi,
args: [address, hash, wrappedSignature],
bytecode: universalSignatureValidatorByteCode,
}),
...rest,
} as unknown as CallParameters)
const callParameters: CallParameters = client.chain?.contracts
?.universalSignatureVerifier
? ({
to: client.chain.contracts.universalSignatureVerifier.address,
data: encodeFunctionData({
abi: universalSignatureValidatorAbi,
functionName: 'isValidSig',
args: [address, hash, wrappedSignature],
}),
...rest,
} as unknown as CallParameters)
: ({
data: encodeDeployData({
abi: universalSignatureValidatorAbi,
args: [address, hash, wrappedSignature],
bytecode: universalSignatureValidatorByteCode,
}),
...rest,
} as unknown as CallParameters)

const { data } = await getAction(client, call, 'call')(callParameters)

return isBytesEqual(data ?? '0x0', '0x1')
return hexToBool(data ?? '0x0')
} catch (error) {
// Fallback attempt to verify the signature via ECDSA recovery.
try {
Expand Down
4 changes: 4 additions & 0 deletions src/chains/definitions/abstractTestnet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,9 @@ export const abstractTestnet = /*#__PURE__*/ defineChain({
address: '0xF9cda624FBC7e059355ce98a31693d299FACd963',
blockCreated: 358349,
},
universalSignatureVerifier: {
address: '0x872146211f996755C8729042093ffb8660F8b129',
blockCreated: 431682,
},
},
})
4 changes: 4 additions & 0 deletions src/chains/definitions/zksync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,9 @@ export const zksync = /*#__PURE__*/ defineChain({
multicall3: {
address: '0xF9cda624FBC7e059355ce98a31693d299FACd963',
},
universalSignatureVerifier: {
address: '0x872146211f996755C8729042093ffb8660F8b129',
Copy link
Member

Choose a reason for hiding this comment

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

Can we get these contracts verified please?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated deployments for the new contract with the new function signature
Contracts are verified:
zksync mainnet
zksync sepolia
abstract testnet

blockCreated: 45659388,
},
},
})
4 changes: 4 additions & 0 deletions src/chains/definitions/zksyncSepoliaTestnet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ export const zksyncSepoliaTestnet = /*#__PURE__*/ defineChain({
multicall3: {
address: '0xF9cda624FBC7e059355ce98a31693d299FACd963',
},
universalSignatureVerifier: {
address: '0x872146211f996755C8729042093ffb8660F8b129',
blockCreated: 3855712,
},
},
testnet: true,
})
24 changes: 24 additions & 0 deletions src/constants/abis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,30 @@ export const universalSignatureValidatorAbi = [
stateMutability: 'nonpayable',
type: 'constructor',
},
{
inputs: [
{
name: '_signer',
type: 'address',
},
{
name: '_hash',
type: 'bytes32',
},
{
name: '_signature',
type: 'bytes',
},
],
outputs: [
{
type: 'bool',
},
],
stateMutability: 'nonpayable',
type: 'function',
name: 'isValidSig',
},
] as const

/** [ERC-20 Token Standard](https://ethereum.org/en/developers/docs/standards/tokens/erc-20) */
Expand Down
1 change: 1 addition & 0 deletions src/types/chain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export type Chain<
ensRegistry?: ChainContract | undefined
ensUniversalResolver?: ChainContract | undefined
multicall3?: ChainContract | undefined
universalSignatureVerifier?: ChainContract | undefined
}
>
| undefined
Expand Down
Loading
Loading