EIP-1344: Add chain id opcode

Alright, I think we can all agree that any discussions of what chainId should be is out of scope for this proposal, but we do need to capture what data type and limits are applicable to the use of chainId so we make sure this opcode captures potential nuance.

EDIT: I also think networkId is out of scope for an EVM opcode because it describes network interfaces not transaction signing

7 Likes

I vote uint256. It will be more code changes in Pantheon but it opens up the meaning of chain_id to more interesting possibilities.

Note that unless you use a chainID > 64 bits parity won’t notice. But I think that should be thrown in test cases and noted as such.

2 Likes

I’d keep it small - 2 billion chains should be enough for anyone. I haven’t seen any proposals where a hash would be used that make any real sense. You can’t hash the genesis block because it’s the same for both sides of a fork, hashing the reference implementation has all kinds of issues around centralisation but also breaking the entire chain every time a new release comes out. Eventually you wind up picking an arbitrary thing to hash and all agreeing on it so why not just pick a number?

1 Like

100% and if you REALLY wanted to do a hash, just slice the first or last (or middle???) 8 bytes. Probably enough entropy there to be used as an ID.

I agree with @shemnon - it should be a 256 bit value.

We are looking to use the Chain ID as our Sidechain ID for our ephemeral on-demand permissioned private sidechains technology. Having a 256 bit value means that we can use the id, a public value, in conjunction with private values to derive cryptographic keys, salts and other cryptographic values. Using a value smaller than 256 bits could open the system up to security issues.

If the value is 256 bits, then if we randomly generate it, it is unlikely to have a collision with an existing sidechain id. If we did have a clash, chances are it is an attack, and we can deal with it assuming it is an attack.

1 Like

I agree, it is not as simple as “Is it expensive or not”

For me it is a combination of, how much gas would be appropriate for such a call and how often would such a call be used.

Probably an argumentation for this should be added to the EIP. Something like:

Signatures are widely used (e.g. state channels, multi signature wallets and relays) currently all these signatures can not be properly protected against cross-chain replay attacks. As we assume that all these signatures will use this opcode it would be preferred to use an opcode, to be gas efficient.

This might be something else if EIP-1109: Remove call costs for precompiled contracts would be part of the next hardfork too

I think you got the likelihood of use down, the alternative to the current design is that everyone either manually configures a constant in their deployed bytecode, or adds a deployment parameter which requires deeper state variable access to read. In aggregate, especially as these solutions get deployed more, it is probably causing a worse impact to the state growth of Ethereum than it would to query CHAINID through a new opcode.

I think it’s more reasonable to have a smaller chainId integer but since EIP155 did not specify it we now have a few chainId’s with over 64 bits so I think we should just use uint256. Also it would match the current EIP712 spec.

1 Like

I suggested this proposal be added to the All Core Devs call #59 for further discussion of implementation and bringing it to Last Call for inclusion into the Istanbul roadmap.

2 Likes

I should be able to participate in the call :slight_smile:

Just as a reminder to myself, the open questions are:

  • OpCode vs Precompile
  • Size of chainId value (uint256 or uint8)

Did I miss something?

3 Likes

I think it’s largely decided to be an opcode, a pre-compile would not make sense for this functionality I do not think. The data type is an interesting question, I think it needs to be at least uint32, but uint256 (one machine word) would match the EIP-712 specification.

1 Like

Added this EIP to the Istanbul hardfork EIP through https://github.com/ethereum/EIPs/issues/1935


Edit: Updated from PR to Issue per https://github.com/ethereum/EIPs/pull/1929

@rmeissner, a few housekeeping updates were requested here: https://github.com/ethereum/EIPs/issues/1935#issuecomment-482399373

P.S. started an implementation here:


Edit: Taken care of here: https://github.com/ethereum/EIPs/pull/1936#issuecomment-482434400

@rmeissner Please approve!

1 Like

I’d like to re-iterate this after today’s dev call, around 41:59. Snippets:

@fubuloubu: If a hard-fork does occur, then code on one chain won’t be replayed on another chain.
@holiman: No… (…) We never change the chainids… are we?
@fubuloubu: Like an actual contentious hard-fork, where there’s two communities.
@holiman: Ri-i-ight… But…
@fubuloubu: So, they would change their chainid.

This is the core of the misunderstanding.

In an actual contentious hard-fork, both sides place claim on being “the one True chain” (refuse to let go of memetic artifacts: these are valuable); conceding and changing chainid is a move that weakens the claim, so it’s unlikely to happen.

As @fulldecent highlights, the issue has been side-stepped in the case of TheDAO split, because chainid was introduced after the fork1; no magic number was already more “valuable” than another, so any could be picked.


The proposal, as it stands, does not guarantee replay protection in case of a contentious hard fork. For that to be true (procedurally), a chainid must change; but, as Martin said, we have no procedure ATM to change chainid - neither who does it, or when. It has also never previously happened.

What it can be useful for is same-code same-address deployments across multiple chains.2 For example, ones that want to use ENS on both main-net and Ropsten.3 Or something like a cross-chain bridge…


On a more abstract level, there is no way to future-proof against “undesirable splits” without specifying exactly what to consider “undesirable”. Essentially, writing a fork oracle once the point of contention is known.4

A blanket condition like “chainid changes” won’t cut it, and - I’d argue - is counter-productive: both sides of the split will want to maintain contracts’ behaviour as it was before the split. This is no longer just a struggle for mind share, but now also a question of “how much of the ecosystem will act up?”


1 This was the first time that importance of cross-chain transaction replay protection on value-bearing networks was demonstrated.
2 This is a niche use pattern, and I haven’t seen many people do it; certainly not brand-name projects.
3 The ENS Registry contract is at different addresses; the TLDs are respectively .eth and .test for main-net and Ropsten; etc…
4 It’s a lot of fun!

1 Like

Thanks for the ping.

Regarding bits

32 bits is insufficient for a chainId, for sure. Even without trying we would seeing unintentional collisions soon. Right now we are considering intentional choices of chainId (“today is nice weather, I think I will choose chainId 42 for my network and nobody else should.”). But in the future there will be many more chains and they will be created programmatically. So we need to be concerned with unintentional chains. The birthday problem says we only need 65k networks for that to happen. This will happen in the foreseeable future.

Regarding same code deployed at the same address on multiple chains

This is specified in ERC-1820 and is currently deployed.

Regarding what is a reference implementation

A reference implementation is some code in any language that is compatible with every other client on the network. Most importantly it is an identifier (a URN) that will not be confused with any other identifier. The easiest, decentralized way to do this is to publish reference implementation code and hash it. Ideally this implementation will isolate only the consensus part (validating blocks) and the hashing for-loop/P2P/storage will be a separate program/process/module.

Regarding contentious fork

This is the tongue-in-cheek explanation that best illustrates the problem with a contentious fork. Here is more detail on how an upgrade would work when a chainID = hash(code | genesis).

All use cases assuming current block is 1,000,000. The current chainId is 11af1af2989...

Use case 1: normal upgrade that everybody wants

Case study: Tangerine Whistle, block 2,463,000

  1. Hudson publishes on Ethereum.org (could actually be anybody publishes anywhere) to upgrade your client for Pimpled Frog upgrade on block 2,000,000. New chainId uses same genesis and hashed with the new software is 22af2af3562...
  2. All miners upgrade to run the new software
  3. From 1,000,000 to 1,999,999 the CHAINID opcode returns 11af1af2989 ...
  4. Truffle, MetaMask, Opera and more update to know about 22af2af3562...
  5. End users sign transactions using 11af1af2989... AND optionally 22af2af3562...
  6. Some transactions get included in blocks up to 1,999,999.
  7. After 1,999,999 all the pool transactions that were signed with 11af1af2989... are discarded by new miners.
  8. The old network continues to exist and process 11af1af2989... transactions. But nobody cares about it.

Use case 2: Contentious fund recovery starts a new viable network

Case study: DAO Fork, block 1,920,000

  1. Same
  2. Some miners upgrade
  3. Same
  4. Some end-user software upgrades
  5. Some end-users DO NOT sign transactions using the new chainId
  6. Same
  7. Same
  8. Some people continue to care about the old network

Use case 3: Aborted upgrade

Case study: Constantinople upgrade, block 2,675,000

  1. Same
  2. Same
  3. Same
  4. Same
  5. Same
  6. Same
  7. Most miners downgrade to previous software before block 2,000,000
  8. The new network is created but nobody cares about it.

Use case 4: Failed contentious upgrade

Case study: This could conceivably happen if Stiftung Ethereum, Zug (ethereum.org) publishes a recommendation to retrieve funds from the Parity wallet.

  1. Same
  2. Some miners upgrade
  3. Same
  4. Same
  5. Same
  6. Same
  7. Same
  8. The new network is created and some people care about the old one and some people care about the new one.
1 Like

Yeah, this was primarily what I was thinking would be valuable.

I definitely see your point here, but this does leave a path for upgrade to systems that make use of this additional opcode. Since the opcode check (in the example EIP-712 use case) is for the present value of chainid that means any off-chain transactions from the point of update could re-target the new chainid, and all old messages would be unusable.

You are definitely right that this does not motivate a sustained fork to change their chainid, but the use of this opcode would allow for an “automatic upgrade” of that off-chain signing functionality versus an immutable value sent on deployment (or worse: a maintained value by the original developer). This may actually create a motivation by users of the opposing fork to convince operators of the new fork to upgrade, so that their application activities can be separated from their application activities. It’s a big game of chicken of course, but that added friction must get resolved one way or another.

This is all highly hypothetical of course, but interesting to think about.

1 Like

So I think this line of reasoning is very sound for Ethereum transactions signed with chain id meant for the Ethereum network directly.

I think the conversation changes a bit when you consider off-chain signed transactions meant for Layer 2 and/or meta-transaction use cases


This is specified in ERC-1820 and is currently deployed.

This requires you actively registering your interfaces with a registry, and doesn’t really solve the chain-specific domain separation issue.

I feel like this conversation may have again gotten a little off track.

This EIP is a net benefit because it aligns the domain separator protection of base layer transactions (e.g. an Ethereum transaction) with those of Layer 2 and metatxn signed messages. It avoids the human error of current solutions and is a directly applicable to a widely accepted and imminently useful standard (EIP-712) that is being implemented in multiple libraries and clients.

There doesn’t seem to be any technical concerns with this approach, we’ve fully specified the implementation, the topic has been discussed on ACD and recommended for inclusion into Istanbul. Are there any issues with moving the status to Last Call with the proposal in it’s current form?

1 Like

The specification is technically complete and is eligible to proceed to Last Call.

We don’t yet know which implementations will use this. But I suspect that all of them will introduce problems compounding on the issues detailed above. Especially off-chain and layer-2 transaction applications.

1 Like

I’m still not sure I understand this sentiment. Taking EIP-712 as an example, if chainId is used in the domain separator, then it currently has to be either a deploy time constant, or a parameter controlled by some trusted third party with higher access control to be upgraded. A contract’s code or deployment procedure also has to account for this nuance when deploying to different test networks and the main network, so it very obviously introduces human error into the process.

This proposal simply aligns extra-protocol message signing using chainId as a domain separator to in-protocol transaction signing that also uses it for the same purpose.

I agree with you that it is difficult to ensure chainId is updated on one fork in a contentious fork event, but that’s outside the scope of this proposal, and it also affects in-protocol signing in the same fashion. I would largely argue it has to be resolved one way or the other in this kind of event if the two forks are to co-exist peacefully as it’s the primary method for replay protection between chains.

By aligning off-chain and on-chain signing, there is more friction ensuring this eventually gets resolved.