EIP-1344: Add chain id opcode

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.

First, everything we are saying is hypothetical because there are no deployed off-chain or layer-2 applications to study.

When they are available some will surely make the mistake of assuming that a chainID will not change ever. Similarly they will probably make the mistake of assuming that the consensus client will never change. These are unrelated but both demonstrate sloppiness.

My sentiment is simply that chainID currently has a known weakness. And this proposal is to weld the chainID onto the EVM.

At the same time, of course it is quite simple to deploy an oracle (using the same account on each network) to return the chainID. I would prefer this approach until applications are better understood.

Working on it! :grin:

How is it our job to babysit developers on how to use an opcode? The EVM is not a safe environment, everything is “use at your own risk”. You also don’t have to use the opcode if you don’t want to.

This seems like a poor solution to me. It relies on a trusted third party and uses an excessive amount of gas for a simple operation that is easily accessible contextual information already available in a transaction.

I don’t think this is good reasoning to block implementation of this opcode in the Istanbul fork. If the method of chosing chainId has a problem, that is out of scope of this EIP and we should not be making value judgements for what is ultimately an established part of existing standards just because it has “potential” complications in a few corner cases that don’t even affect any existing applications (as you already noted!). This proposal doesn’t make it worse than what it already is.

1 Like

Imagine a scenario where this proposal wasn’t implemented, and an alternative like an on-chain oracle were in use. If the value produced by the oracle mismatched what the protocol says, this would be a potential griefing attack. EIP-712 messages signed with a chainId domain separator would use the RPC provides value to sign with, which now mismatches the chain. This means all messages that are signed would get rejected, and the oracle becomes a critical piece of infrastructure that limits the amount of value placed on any Layer 2 solution. This griefing mechanism exists even outside of a change to the value of chainId.

Conversely, this proposal implements it in the EVM as a feature, directly matching the protocol’s value with no additional trust required. Sounds like a safer option to me!

I propose an alternative
Instead of having an opcode that return the latest chainId, we should have an opcode that given a chainId as input return true if the chainId is part of the history of chainIds of that chain, false otherwise.

This is compatible with an hashing system like @fulldecent propose and it ensures offchain messages signed in the past still work in the future across future forks.

When there is a chainId updates wallet would still protect users by ensuring they sign with the latest chainId.

1 Like

I think a lot of these discussions revolve around the fact that currently Dapps are not designed to take chainId into account.
Mostly because they have adopted the pattern recommend by Metamask to check for the net_version instead plus they rely on Metamask to refresh the webpage whenever there is a network change. Hence there are a lot of assumptions on how Dapps are developed nowadays.
Regardless we should build standards to allow these patterns to change and provide best practices about how to handle the state of a Dapp. For example, a good start is how EIP-1193 includes an event subscription for network changed.
Additionally Dapps should always check with the node the current chainId, using the eth_chainId method first introduced with EIP-695.
Given these progressive changes we will see the need for EIP-1344 become more apparent as we will design Dapps to track the chainId more closely. Especially for meta-transactions and layer 2 solutions.
As already stated above, it’s actually very important for using EIP-712 messages.

1 Like

This is an interesting proposal, thank you.

So, I’m not sure about this as a requirement, but let’s explore.

Assume a truly contentious fork occurs, and chainId is eventually resolved such that the fork of lesser community size is socially coerced to adopt a different value (if they refuse, this proposal does not add value). Also assume that a subset of applications that rely on the value of chainId either prefer the minority chain, or wish to continue supporting both chains in the fork.

Therefore, messages signed on the majority chain only require the domain separator for the old chainId, which has the hash 0x1234. The minority chain must accept the old hash 0x1234, or the domain separator includes the new value for chainId, which has the hash 0xABCD.

On the minority chain, both should be accepted, although to be strictly accurate we should ensure that only those messages signed after the fork date use the domain separator hash 0xABCD or else we can replay a user’s signed messages from after the change on the majority fork. So, we need to delegate to an oracle to ensure that we have proper ordering of time for these signed messages, or we risk the above happening (another opcode would be too complex to specify).

I think all in all, this is too improbable a scenario to try and design some explicit mechanism to handle. I think the added friction of this scenario occuring would ensure that the minority chain has little support from the builders of these applications, because users will have to jump through some hoops to get their old signed messages processed in the case of a contentious split like that described above.

The only alternative I can think of where this is not true is the one where the Ethereum Foundation, who has registered the trademark, is the supporter of the minority chain, meaning they forced a split that community does not find to be legitimate. However, the trademark has nothing to do with the value of chainId and I would think the majority fork community would not prefer to modify their value if a hostile fork occurs, and instead try to socially coerce the minority chain (in this scenario, run by the EF and very few other parties) to change theirs because the community does not recognize the minority chain as legitimate.

This is way down in the weeds, I hope you’ll agree. 99% of the use case for this opcode is for replay protection of testnet transactions.

This is not so complex situation that you seem to make it (unless I misunderstood something :slight_smile: )

The idea was that on every fork a new chainId is generated (hence me pointing to @fulldecent proposal) so if part of the chain community disagree with the changes being made as part of a fork, they simply need to create their own fork to keep the old behaviour except for a new chainId.

After that, all off-chain message signed before the fork will still work in both forked chain. A desirable property in my opinion since it allow users to choose which fork they use without having their current use affected.

But note that updated wallet will make sure new message are not signed with old chainId since these message could be replayed.

Sorry, I edited my response to say “improbable” versus “complex”. I believe this to be a fairly unlikely event, but certainly possible.

It is possible for replays to occur after the forking event on the updated chain since both chainIds are valid there. You need a time oracle to prevent this.

Sorry, I edited my response to say “improbable” versus “complex”. I believe this to be a fairly unlikely event, but certainly possible.

I am actually not sure what you were trying to say with your scenario. From what I understood it is solved if both fork get a new chainId.

It is possible for replays to occur after the forking event on the updated chain since both chainId s are valid there. You need a time oracle to prevent this.

I don’t think we need time oracle. We simply assume that every message that was signed with an old chainId are considered valid for both chain. In other words such message should be replayable on all chain that have that chainId in its chainId history. That is how we define it. This is similar how old transactions are included in both chain except that for offchain messages, it is the responsibilities of the wallet to use the latest chainId

And wallet supporting the “chain that forked in the first place” will be aware in advance of the fork and could deal with the update itself.

Similarly wallet supporting the “chain who forked simply to get a new chainId”, can do the same.

Regarding gas. The gas savings of implementing CHAINID opcode is zero. Currently, chain ID be gotten for ~800 gas (G_sload) from a naive oracle implementation, or it can be hardcoded in a contract for 3 gas (G_verylow). This new approach would reduce that to 2 gas (G_base). The gas savings calculation for adding an opcode is:

savings = Δgas × number_of_uses

Here, the calculation is:

0 = 798 × 0

Compare to EIP-145. Their equation is approximately:

a very lot = some × a lot

In summary, from the perspective of gas, CHAINID opcode is a premature optimization. The argument in favor of CHAINID opcode on the basis of gas savings would be much stronger if people were added to this discussion who are currently using the ~800 gas oracle approach or the 3 gas hardcoded approach and see other merits of this addition.

Regarding safety. It is not our job to babysit developers. By explaining the potential problems with CHAINID opcode I illustrated that the use cases are not yet well known enough to say if it provides any value whatsoever.

Regarding trusted third party. It is simple to create an oracle that works on all existing networks that returns the correct chain ID for ~800 gas. After that is done, no further trust is required. If additional networks are created they are welcome to also implement the oracle. This is implementable TODAY. We can open a separate thread if anybody is interested in this. If nobody is interested in this then clearly CHAINID opcode is not needed.

Regarding “why not?” There are many things which can be implemented. For example, an opcode which concatenates strings or rotates the top three stack items would be very useful. The threshold for adding new features should not be “this proposal doesn’t make it worse than what it already is”. The threshold should be “this feature is badly needed, workarounds are already in widespread use”.

Regarding what if the oracle is set up incorrectly. Then it would work incorrectly.

Regarding an alternative to find chain ID histories.

Thank you for the proposal. I believe the best solution is to have two new opcodes: GENESIS returns the hash of the genesis block and CONSENSUSCLIENT returns the hash of the consensus client used to validate the last block.

This approach with a simple trustless tool allows finding the lineage of any block and maintains usefulness on both sides of a contentious or non-contentious fork.

Regarding building for the future.

^ Yes, we should build standards based on the future and a good understanding of the use cases.

Regarding Ethereum Foundation.

I disagree, in the event of a contentious fork, both networks would call themselves “Ethereum”. If the Ethereum Foundation supported version is the less used fork then they may assert trademark rights against people using the other network. It is undocumented whether they would do this. A request to document this is here https://github.com/ethereum/ethereum-org/issues/841 This is a fundamental issue preventing enterprises from using Ethereum mainnet.

Does the trademark include chainId? No. Can you trademark a number? I’m pretty sure the answer is no, but lawyers are creative.

I don’t think this line of argument holds any value to this discussion.

There are currently 3 production Plasma implementations, with more coming. There are 2 production state/payment channel implementations, with more coming. All of these implementations are exploring upgrades to incorporate EIP-712 when client libraries add support, so while your calculation is currently correct, it will not be for very long. The only safe way to implement this in contract code is through a state variable, which reduces the cost estimate to 198 from your oracle solution (which I do not believe to be safe). Since L2 will see increasing prominence as more solutions come online, I believe this equation is therefore:

198 x a very lot = a very, very lot

So, I contend you are drastically underestimating the potential impact, but that’s fine we can use a state variable to store this value in the mean time while we consider the potential defects in this approach.

I do not believe an oracle to be a sound solution here.

I am not sure, but it feels that the original purpose of the EIP is left out in a lot of these discussion.

We have EIP-155 to protect regular transactions agains replay attacks. And the (simplified) purpose of this EIP is to enable something similar for signatures.

It would probably also be possible to change “ecrecover” to somehow handle this, but that would be way more complex.

Concerning the validity of a signature after a hardfork: my expectation would be that my signature is only valid on 1 of the chains.

Concerning the change of a chainID after a hardfork: while it is true that there is no process, the case of ETC shows that a community will choose a new chain id to separate themselves from the other chains. Why did ETC choose a different chain id? I am pretty sure, not just because the ETH community said so.

For me the implementation via an opcode is the simplest way, but having it as a precompile would also be a big improvement (over having nothing). Any oracle deployed by a third party needs somebody who updates it, therefore I don’t think this is a viable solution.

I think this is your principle argument. There are lots of examples of how this will be used in practice, and shortly, because EIP-712 supports these use cases being safe. If you have an issue with using chainId as a domain separator, I think you should have brought it up during the standardization process of EIP-712.

This proposal is very simple. It does not have to be used. It enables clear support for an existing use case that is seeing more support recently, it reduces human error, and it saves a small but not insignificant amount of gas. It aligns directly with the protocol, instead of leveraging trusted third party solutions like oracles.

Yes, in some ways I am trying to build in support for use cases that are not widely demonstrated yet, but many see the utility in having this available, and I think it would be better to have this available than to design for potential scenarios that have only occurred 1 time in the past 5 years of the project, and also have a few potential mitigations. How we set chainId could be changed in a future discussion, leveraging some hash-based mechanism for modifying it, but that is out of scope of this proposal, which aligns L2 use cases with the protocol domain separator that clients will directly use to sign L2 messages.

1 Like

I think actually there may be a good strategy to this that we can incorporate as a mitigation if those building with this opcode choose to protect against. They would cache the value of chainId in contract storage and compare every transaction against it (200 gas), and update the value if necessary, also logging the time.

This would protect against an upgrade of the value this opcode provides, without relying on a oracle or pre-compile contract. Newer contracts wouldn’t cache the older values if they’re deployed post-fork, protecting later deployments from replay attacks against earlier fork values that your proposal may expose them to.

I do think your concern is legitimate in that we should ensure a trustless and seamless upgrade for this value in the case of contentious hard fork, but I don’t believe an oracle provides this, and your proposal leaves a bit of hole after the value upgrade occurs in that presently signed transactions can be replaced after the fork (without a more complicated, time-based mechanism to ensure transaction ordering prior to the fork)

I just want to chime in and say that:

  • we use EIP-712 to sign meta transactions from our mobile SDK
  • we use side chains (potentially many) and want to prevent replay attacks

It seems to me that the original proposal would be very beneficial. At the same time I don’t see any downside.

I also don’t see a viable alternative proposed so far.

Maybe @benjaminbollen or @pro want to add something as they are more involved with the meta transactions than I am.


your proposal leaves a bit of hole after the value upgrade occurs in that presently signed transactions can be replaced after the fork

What do you mean by “replaced” ?
If you mean replayed, that is for me completely fine. I consider a message signed in the past belonging to the pre-fork era. It should thus be valid in all future fork. Like a transaction included in a chain is present in all future fork. This is actually a very important feature. Imagine you play a state channel based game and you hold your opponent losing move’s message. If this message get invalid because of a chainId update, you would lose your opportunity to grab the price. This is not what we want. We actually want such message to be redeemable in all forks. Think of these set of messages as part of the state of pre-fork era.

I might not understand a crucial component of your concerns though. Any way to give a concrete example?

As for newly created message, as mentioned this will be the responsibility of wallet to ensure such messages use latest chainId (to avoid replayability of the message in other forks)

1 Like

Ah yes, I think I see the disconnect.

So, imagine a split has already occurred, and the minority chain accepts both the old and new chainId. If time is not taken into account (and specifically the time of the fork/chainId upgrade) then it would be possible to replay messages signed for the majority chain after the fork date on the minority chain. You would need some mechanism for ensuring the time the message was signed was prior to the fork date, whether through timestamps or an ordering mechanism (such as Plasma transactions referencing the prior block)