Skip to content

feat: allow ERC-1271 contract signers in MultiSigDeleGator - #214

Open
Muhammad-Altabba wants to merge 1 commit into
MetaMask:mainfrom
web3web4:feat/multisig-erc1271-signers
Open

Muhammad-Altabba wants to merge 1 commit into
MetaMask:mainfrom
web3web4:feat/multisig-erc1271-signers

Conversation

@Muhammad-Altabba

Copy link
Copy Markdown

Closes #213

What?

  • MultiSigDeleGator accepts smart contract accounts as signers next to EOAs. Their signatures are checked with ERC-1271, and one multisig signature can contain both types.
  • The functions that set signers (initializer, reinitialize, updateMultiSigParameters, addSigner, replaceSigner) no longer reject addresses with code. They still reject address(0) and the DeleGator's own address.
  • documents/MultisigDeleGator.md describes the signature format, a nested example, and the trust, gas and bundler considerations.

Why?

See #213. In short: so that nested multisigs, passkey users (HybridDeleGator) and EIP-7702 accounts can be signers.

How?

Signature format

Exactly threshold entries, sorted by signer address, as before. Every entry starts with 65 bytes. The last of them (the v position of an EOA signature) marks the type:

  • EOA signer (unchanged), v is 27 or 28: r (32) | s (32) | v (1)
  • Contract signer, 0: signer (20) | length (4) | zero (40) | 0 (1), followed by length bytes of signature

Validation

  • EOA entries are checked with ECDSA.tryRecover. Contract entries are checked with OpenZeppelin SignatureChecker.isValidERC1271SignatureNow, and only after the signer is confirmed to be registered (isSigner), so other addresses are never called.
  • Any invalid entry returns SIG_VALIDATION_FAILED without reverting: malformed bytes, non-zero reserved bytes, or a contract signer that rejects, reverts or returns a wrong value. Extra data after the last entry is rejected.
  • Each nested level validates a strictly shorter part of the signature, so validation always ends, also when two MultiSigDeleGators are signers of each other.

Design choices

  • The signer type is not stored. Each entry states its type, so nothing can become stale when a signer's code changes (EIP-7702), and the storage layout is unchanged.
  • The type is in the v position, so EOA entries need no extra storage read. Safe uses the same v == 0 marker, but stores contract data at an offset after all entries (Safe signature encoding). Inline data is simpler to build and parse, especially when nested. The zero bytes keep one valid encoding per entry. The cost is a 65-byte header per contract entry.
  • The 4-byte length allows any practical signature size, including nested ones, and cannot overflow the offset arithmetic.

Behavior changes

  • EOA signers without code: the same signatures are accepted as before, at about the same gas. isValidSignature, main → this PR: 2-of-3 12,576 → 12,479, 3-of-5 17,519 → 17,505, 5-of-7 27,404 → 27,556.
  • An EOA signer with EIP-7702 code can still sign with ECDSA, and can now also sign with a contract entry that its delegated code checks. Its approval is then only as strong as that code.
  • A malformed EOA entry (for example v not 27 or 28, or a high s) now returns SIG_VALIDATION_FAILED instead of reverting in ECDSA.recover, the same as all other invalid entries. The signature is rejected either way. Only the error changes: InvalidERC1271Signature in DelegationManager, and AA24 signature error instead of AA23 reverted in the EntryPoint.
  • The initializer now rejects the DeleGator's own address. Before, the code check missed it when the initializer ran in the proxy constructor.

Limitations

  • Bundlers: contract signers that read their own storage (such as a nested MultiSigDeleGator or a HybridDeleGator) need a staked account (EntryPoint v0.8 or later) or a private bundler for UserOps. Details are in MultiSigDeleGator: support smart contract accounts as signers (ERC-1271) #213.
  • A contract signer that uses up the gas (for example by returning very large data) makes validation fail with out-of-gas instead of SIG_VALIDATION_FAILED.
  • Contract signers that are not deployed yet cannot sign (no ERC-6492).

Tests

New tests in MultiSigDeleGatorTest.t.sol:

  • Adding and replacing contract signers. Mixed EOA and contract signatures, 2- and 3-level nesting, circular signers.
  • A HybridDeleGator signer with a passkey. An EOA with EIP-7702 code added as a signer (ECDSA and ERC-1271 entries), and an EOA signer that gets EIP-7702 code after it was added (ECDSA).
  • A UserOp through the EntryPoint and a delegation redemption, both signed by a nested multisig.
  • Rejection without revert: malformed, truncated, oversized and zero-length entries, non-zero reserved bytes, unsorted entries, trailing data, unregistered or undeployed contract signers, and contract signers that revert or return no data, a wrong value or bad padding.

The tests that expected contract signers to be rejected now expect success (test_error_updateSigParamatersContractNewSigner is renamed to test_allow_updateSigParamatersContractNewSigner) or were removed (addSigner, replaceSigner).

Results:

  • forge test --no-match-contract "DelegationMetaSwapAdapterForkTest|VedaLendingTest": 1052 passed, 0 failed. The two excluded suites are fork tests that need RPC URLs.
  • forge coverage --match-contract MultiSigDeleGatorTest: all branches of MultiSigDeleGator.sol are covered.

Notes for reviewers

  • The main change is in _isValidSignature.
  • VERSION is unchanged (1.3.0). Tell me if it needs a bump.
  • No usage example is included, because clients build these signatures off-chain. I can add a Solidity example in a follow-up PR if you want one.

@Muhammad-Altabba
Muhammad-Altabba requested a review from a team as a code owner October 12, 2026 13:29
- Updated documentation to reflect that signers can be EOAs or ERC-1271 contracts.
- Modified MultiSigDeleGator contract to accept ERC-1271 contracts as valid signers.
- Adjusted signature validation logic to handle mixed EOA and contract signatures.
- Added tests to ensure proper functionality with ERC-1271 contract signers, including nested multisig scenarios and validation of mixed signatures.
@Muhammad-Altabba
Muhammad-Altabba force-pushed the feat/multisig-erc1271-signers branch from e8076e3 to 7dd7d40 Compare October 23, 2026 06:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

MultiSigDeleGator: support smart contract accounts as signers (ERC-1271)

1 participant