ERC-7964: Cross-Chain Signatures for Account Abstraction

Hi everyone! :waving_hand:

Following up on ERC-7803, I’ve drafted ERC-7964: Universal Cross-Chain Signatures for Account Abstraction - a simple extension that enables cross-chain signatures using chainId: 0.

Concept

One rule: chainId: 0 in signing domains = valid on any chain

This allows users to sign once and authorize operations across multiple networks - perfect for cross-chain intents, multi-chain DAO voting, and unified account management.

ERC

Live demo

2 Likes

Thanks @ernestognw — really cool proposal!

One important consideration I’d like to raise:

While chainId: 0 for universal signatures simplifies UX, things may get tricky with Smart Contract Accounts (SCAs) or custom wallet logic.

For example:

  • Contract code may differ across chains (even if the interface is similar).
  • A signature that passes isValidSignature() on one chain might fail or behave differently on another.
  • If contract state (e.g. owner, nonce, or permissions) has diverged across chains, security assumptions break down.

So I’m wondering:
How do we mitigate risks where same signature = valid on one chain but unsafe on another?

Would love to hear thoughts on whether:

  • A checksum of the deployed bytecode should be embedded in the domain separator?
  • We need chain-specific overrides for contracts with isChainAware() logic?

Really appreciate the simplicity this introduces for users, AA kits, Devs.

1 Like

Hey @codebyMoh! Thanks for the the thoughtful feedback!

On scope: You’re absolutely right about the complexity with SCAs. This ERC intentionally focuses on being a simple building block rather than solving cross-chain authentication holistically. The chainId: 0 pattern gives us universal signature validity, but the verification semantics are left to the implementation layer.

On your specific concerns:

  • Code differences: This feels like wallet/dapp responsibility to me. Wallets should warn users when signing for contracts with different bytecode across chains, and dapps should validate contract compatibility before accepting signatures. The standard can’t enforce this on already-deployed implementations, but we should definitely call it out in Security Considerations.

  • State divergence: Similar reasoning - this is where higher-level coordination mechanisms come into play. Things like keystore rollups or dedicated state sync protocols could address this, but probably warrant their own ERCs.

On “unsafe” signatures: I’d love to hear your definition here. In my view, a signature is “unsafe” when it authorizes unintended actions. But with proper wallet UX (showing exactly what’s being signed on each chain) and dapp validation (checking contract compatibility), the risk becomes manageable.

On your solutions: The bytecode checksum idea is elegant! But I lean toward leaving these patterns to implementations rather than standardizing them here. Different use cases might want different safety mechanisms - some might prefer your checksum approach, others might want state synchronization, etc.

Hope this framing makes sense! Curious to hear your thoughts

1 Like

@ernestognw Your framing makes a lot of sense — especially treating this as a foundational primitive rather than a full authentication framework. I agree that universal signature validity is a powerful enabler, and implementation-level flexibility is key.

On “unsafe” signatures:
Yes, totally agree with your definition — signatures are “unsafe” when they authorize unintended actions. I think the risk amplifies in scenarios where contracts diverge in state or logic but remain address-aligned across chains. So UX + validation are critical — maybe the standard could recommend (non-mandate) checksum practices or at least link to best practices in Security Considerations?

On state divergence:
Right, it’s out of this ERC’s scope, but might be worth noting that coordination layers (e.g., keystore rollups, state pinning, chain-specific guards) become almost essential in high-stakes use cases like governance or treasury control.

On modular safety:
Totally fair to defer specific safety patterns to use case–specific implementations. Maybe we could treat the chainId: 0 concept as an interface layer and publish optional “safety modules” alongside it — like a checksum utility, signer attestations, or bytecode validation hooks?

All in all, loving the direction — this could become a key building block for cross-chain AA architecture. Happy to brainstorm or co-contribute to supporting standards or tooling around it if useful.

Thanks for the feedback, @codebyMoh!

maybe the standard could recommend (non-mandate) checksum practices or at least link to best practices in Security Considerations?

I’m hesitant to recommend unproven patterns, and I’m not sure if I would recommend this approach. I think it’s preferable to see real-world usage to inform safety practices rather than speculating upfront.

but might be worth noting that coordination layers (e.g., keystore rollups, state pinning, chain-specific guards) become almost essential in high-stakes use cases like governance or treasury control.

Absolutely agree these become essential for high-stakes use cases. However, I see this as a natural architectural consequence rather than something this ERC should prescribe. Imo, different use cases will have vastly different coordination requirements.

publish optional “safety modules” alongside it — like a checksum utility, signer attestations, or bytecode validation hooks?

While I appreciate the modular thinking, I’m concerned about scope creep. The strength of chainId: 0 is its simplicity. I’d rather see future ERCs build specialized safety frameworks on this foundation if demand emerges. Also happy to provide feedback if such standards appear

1 Like

@frangio pointed out (accurately) that all domains fields are optional. Citing from EIP-712

Protocol designers only need to include the fields that make sense for their signing domain.

So it may be clever to rework this ERC as just omitting the chainId field.

Hey everyone! Thanks for the feedback so far, especially @frangio’s point about omitting chainId instead of using 0. I’ve substantially reworked ERC-7964 based on this and other considerations.

Key Changes

1. Omit chainId instead of using 0
Following @frangio’s suggestion, the ERC now simply omits chainId from the root EIP712Domain since all fields are optional per EIP-712. Much cleaner semantics!

2. Dropped ERC-7803 dependency
The proposal now uses only standard EIP-712 encoding - no draft standards required. This means existing wallets can support it immediately without implementing custom logic.

3. Array-based encoding over Merkle trees
Cross-chain operations are encoded as standard EIP-712 arrays. While proposals like EIL use Merkle trees, I’ve opted for arrays because:

  • For 2-5 chains (typical case), Merkle trees save only 32-64 bytes
  • Arrays provide full transparency in standard wallet UIs - users see all operations
  • Merkle trees require custom wallet logic to verify leaves aren’t malicious

4. Introduced EIP712ChainDomain pattern
Each chain-specific operation includes a nested domain with chainId and optionally verifyingContract. This handles both:

  • Same address deployments (CREATE2) → root domain has verifyingContract
  • Different address deployments → nested domains have verifyingContract

Updated Examples

All four reference implementations now demonstrate both deployment patterns and include reference on-chain verification code.

Would love feedback on this simplified approach! Does the array encoding trade-off (transparency vs. minimal extra overhead) make sense for the typical 2-5 chain use case?

great proposal

here’s something we developed couple of years ago, which may be helpful

@fmc Thanks for the SmartSessions pointer. Good prior art and useful confirmation that the core idea holds up in production: omit chainId from the EIP-712 domain, push it into an array of per-chain entries, and use an index to point each chain at its own slot. MultiChainSession { ChainSession[] sessionsAndChainIds } with chainDigestIndex is essentially the same shape as SetValue { ChainOperation[] operations } with structIndex.

The places where ERC-7964 tries to generalize beyond what SmartSessions encodes:

  1. Generic signature envelope, not session-specific. SmartSessions defines the encoding for the ERC-7579 enable flow. ERC-7964 specifies a standard envelope (bytes32 header, bytes32[] structsArray, bytes crossChainSignature) with a 9-byte magic prefix 0x796479647964796479 so a contract that normally calls isValidSignatureNow can detect a cross-chain signature inside the same code path. The goal is reuse by any application doing EIP-712, not just sessions.

  2. Per-chain verifyingContract in each entry. EIP712ChainDomain { uint256 chainId, address verifyingContract } lives inside each ChainOperation, so applications deployed at different addresses across chains stay covered without needing a deterministic deploy.

  3. ERC-5267 indirection for the domain. The header carries an application address pointing at an ERC-5267 source on the current chain, so the verifier reconstructs the domain separator on-chain without trusting the signature blob to carry it.

So 7964 is less “new mechanism” and more “lift the pattern SmartSessions has been using into a standard signature shape that any app can opt into.” Curious whether anything in the SmartSessions experience would push back on the envelope choice (single 32-byte header + ABI-encoded tail).


Separately, here’s an interactive demo that may help readers see the mechanics in motion.

The demo targets three testnets (Sepolia, Arbitrum Sepolia, Base Sepolia) and a minimal CrossChainAppMock that exposes a single setValue(address account, uint256 value, bytes signature) entry point. You pick a value, sign once, and then submit on each chain.

The interesting bits to look at:

  1. Sign tab, typed-data preview. The wallet receives a domain with only name + version. No chainId. The per-chain chainIds sit inside each ChainOperation instead, so the signer reviews and authorizes the concrete set of target chains in the EIP-712 popup. Any wallet that already renders EIP-712 typed data shows this correctly. No custom wallet support needed.

  2. Submit tab, header byte strip. Each chain card shows the 32-byte header as a colored strip: gray magic (9B) and blue fields (1B) are identical across chains; red structIndex (2B) and purple application (20B) differ per chain. The crossChainSignature and the structsArray next to it are identical on every card. The only thing that changes per submission is the 22 bytes that point this chain at its own slot.

  3. One signature, three submissions. The same 65-byte ECDSA signature satisfies all three contracts, because each one reconstructs the same EIP-712 hash from the shared structsArray and its local domain.

Integration-side it stays small: an application that today does SignatureChecker.isValidSignatureNow(...) can swap to CrossChainSignatureChecker.isValidCrossChainSignatureNowCalldata(...) (per the reference implementation in the ERC) and get cross-chain semantics without any account-specific logic. The wallet UX and the signature primitive are unchanged.

Hey @ernestognw

I’m researching EIP-7999’s Universal Overflow design : ethresear.ch/t/gas-overflow-for-multidimensional-fee-markets/24766 and trying to understand its contract compatibility implications.

One thing I’m struggling with is separating:

  • assumptions developers genuinely rely on today when using gasleft() and gas-limited CALLs, from

  • assumptions protocol researchers believe developers rely on.

For example, Universal Overflow appears to preserve the classic “retain gas after a subcall” pattern, but it may no longer preserve the interpretation of GAS as a measure of total remaining execution capacity.

My current hypothesis is that most compatibility risk isn’t around retained-gas patterns themselves, but around contracts that use gas observability as a runtime decision signal.

As part of this work, I’m speaking with protocol engineers, smart contract developers, auditors, and tooling teams to collect a broad set of perspectives and real-world examples. I’m trying to understand not just whether the design is theoretically sound, but whether it aligns with the mental models and assumptions developers actually use in production.

Given your experience with production contracts, I’d love to hear whether that intuition sounds right or completely wrong.

Would you be open to a short 15–20 minute chat sometime this week? Even a few quick thoughts over messages would be incredibly helpful :folded_hands:

Thanks,
Ifeoluwa