EIP-1344: Add chain id opcode

Yeah, I think we basically understand the discussion here. I might try and implement a simplistic version of the necessary mitigation to use as a comparison again the other proposal.

I think that proposal needs more discussion, so I would agree that we should move any further suggestions along those lines there unless there is a strong concern with this specific proposal that is not already captured.

Hi, I reviewed the current rationale and from the discussion we had here and on EIP-1959, I think you should update to mention the following points :

  • the fact that contract can naively check chainID == message’s chainID makes the opcode potentially dangerous to use
  • It already mention the need for a caching system but it would be good to indicate that alternative solution do not need it
  • It also does not mention the minority-led hardfork situation (as described here) . In particular:
    • with such caching system, it is actually not possible to properly protect from replay in case of a minority-led hardfork since the caching system will not accurately save the blockNumber at which the chainID became the latest. As such during the gap, messages signed with a blockNumber present in that gap, from the majority chain will be replayable on the minority chain even though they should not
    • Even then, it does not mention the minority-led fork situation where the messages will need to include the blockNumber representing the time it was signed so that the contract can prevent it from being used across such forks
    • And it also does not mention that it suffice only one important application to not protect using blockNumber for the minority-led fork to be at a disadvantage as such application will suffer from replay in that fork.
  • I would also mention as I did in EIP1959, the potential benefit in separating chainID from the domain separator since the chainID will change (but as mentioned a domain separator caching mechanism could work too)

While I do still think that having this opcode would be a bug step forward I think it is also pretty clear that there needs to be a separate discussion concerning what is the purpose of a chain id and how is it represented.

The case that a message should be valid on different chains (= chains with different chain ids) is not only related to signed messages, but also to normal transaction and I don’t think this was never really discussed.

In the core dev event last week the question came up if it is possible to design the chain id in a way that you can basically check both cases.

Also as mentioned before it might actually be cleaner to adjust ecrecover to be able to handle a chain id that is encoded in the v parameter (same as for a normal transaction).

It would also be interesting if there are other use cases besides signed messages for this opcode (since ecrecover changes would cover this too)

For some use cases this is still perfectly safe behavior, even if it changes values e.g. meta-transactions.

I think the pros and cons are well captured already. It’s a bad practice in my opinion to mention other proposals within a proposal if there are not explicit dependencies.


  1. A minority-led fork is a special case of the scenario where one side changes chain ID and the other side doesn’t. This scenario is already mentioned.
  2. If the minority fork doesn’t implement replay protection, then yes replays are possible. That should be obvious.
  3. A properly-implemented caching mechanism can be purely trustless in this scenario, as anyone can operate it (it may even be included within the application’s user flow).

Also, there may be application-specific scenarios where forcing the use of fork block height to differentiate changes in chain ID would be unnecessary, and potentially even harmful. The example given in the other thread was that of Plasma’s message structure (a fairly large use case for EIP-712 and this proposal).

In the Plasma scenario, there are blocks being managed asynchronously, and the Operator is tasked with publishing them on a regular interval. As a part of the publishing logic in the on-chain Plasma smart contract, the domain separator used could be trustlessly updated (using a caching mechanism), and all downstream clients would verify that the EIP-712 signing mechanism used the same domain separator as what would be required to validate the message on-chain later on. This creates a lock-step upgrade ensuring messages don’t fall into corner cases, which would not be possible with a strictly fork block height-based mechanism.

It may not be the best case scenario to mandate a particular style of handling this requirement for all possible applications, mostly because we don’t know what those will look like. I liked EIP-1959 because it gave the option to use that mechanism if it made sense for your application, and it made it easy to do so. I don’t think it replaces this proposal as succinctly.


This could indeed be mentioned (in both scenarios) but this should be rare enough (at least under the current process for changing chain ID, of which there isn’t any) that it may be unnecessary to make the distinction. We are after all talking about a scenario that has happened exactly 0 times in the past, and is only projected to occur because EIP-155 was added after the DAO fork to solve a potential issue that was now possible.

It may be unlikely, but another fork like that could never happen again, or it could be 5-10 years away from happening again! Even if it happened on a yearly basis (we do hard forks every 9-12 months currently), I doubt it would be considered burdensome to the application to handle it with a properly-designed caching mechanism.


I definitely agree, and this fits the framework we’ve been discussing that potentially a hash-based mechanism for setting chain ID might be created which updates this parameter automatically on every fork (e.g. @fulldecent’s proposal).

Do you mean that messages signed with older chain IDs in an off-chain signing process? As mentioned, it is application specific, and I don’t think mandating a particular style is helpful as we are likely to miss something.

An example of something we do need to think of is that all transactions in the unconfirmed pool are signed with a particular value of chain ID, and those messages would get dropped by the protocol if that changes. This might be desirable behavior, basically cleans out the unconfirmed pool on a regular basis if hash-based chain ID were adopted.

I think further discussions of these types of things of how chain ID is set is out of scope of this proposal, and we should pick up another discussion thread for figuring this out. I’m fairly confident at this point that with this proposal we can handle any scenario required out of those discussions.

For some use cases this is still perfectly safe behavior, even if it changes values e.g. meta-transactions.

The problem is not that it is safe in some behaviour, the problem is that it can be easily used wrongly. If an application use it wrongly by assuming that simply checking chainID for equality is enough and a hardfork that change the chainID happen, the application get broken.

On that note contrary to what you say, meta transactions themselves would benefit from accepting past chainID. I would actually argue that is indeed unsafe to not do it as there are scenarios where the user will be unavailable to resubmit the meta tx in time. The fact that you do not see it prove that it is easy to used that opcode wrongly.
When users send metatx, they assume the tx will be executed. They don’t expect to have to sign it again (not just resubmitting, as a relayer could do it) later. If the meta-tx smart contract do not allow for past chainID, this assumption break easily at fork transition. A fork should ideally not bother the users. A no downtime solution should always be the goal. So if we can achieve this, we should do it.

It could be argued that around the fork time, wallet could already force the use of future chainID (and relayer would then wait that time before publishing the metatx) but this assume they know what the message is destined to be use for.

A minority-led fork is a special case of the scenario where one side changes chain ID and the other side doesn’t. This scenario is already mentioned.

As mentioned, that special case (which you agreed was an important concern in EIP-1959 discussion. You even pointed that as the weak point of EIP-1959 since it simply return a boolean) can’t be handled properly via the opcode proposed here (see below). As such it should be mentioned in the EIP.

  1. If the minority fork doesn’t implement replay protection, then yes replays are possible. That should be obvious.
  2. A properly-implemented caching mechanism can be purely trustless in this scenario, as anyone can operate it (it may even be included within the application’s user flow)

These 2 points show a misunderstanding of the issue.
The smart Contract in question would have replay protection using the caching solution mentioned in the rationale. It thus receive the chainId from the message (and let say it also receive the block number representing the time at which the message was signed) and check it against the cache.
Now let say a minority-led hardfork happen at block X so the minority fork has a new chainID from that time onward while the majority chain keep the same chainID (since it did not fork)
Now let say a user of both majority and minority chain signs a message for that smart contract at block X+1 and want it to be applied to the majority chain only (the purpose of replay protection). Unfortunately the user has no choice but to use the majority chainID which turn out to also be a past chainID of the minority chain (It would already be cached there).
As you can see, a replay is possible. Now the way to prevent the problem is to use the blockNumber provided as part of the message to check if the message block number happen after the fork (if it does then it should not be replayable). Unfortunately since the cache has a delay inherent due to being implemented as a normal smart contract the replay will still be possible for message signed at blockNumber smaller than the blockNumber at which the cache was updated with the latest chain ID.

EIP1965 does not have this issue and allow thus minority-led hard fork to happen without being exposed to replays, This preserves an important decentralisation component of blockchain where we should give equal opportunities to every communities that want to fork away. Of course, in the case of EIP-1344 the replay window is small but that could be critical in some situation.

Also, there may be application-specific scenarios where forcing the use of fork block height to differentiate changes in chain ID would be unnecessary, and potentially even harmful. The example given in the other thread was that of Plasma’s message structure (a fairly large use case for EIP-712 and this proposal).

You simply showed there that plasma did not need to use the precise block number at which the chainID changed.
As for forcing the use of a block number, this is not true. You can always provide your own. In other words both can be used, the application specific block number and the block number assigned by EIP-712. The point is that in order to guarantee replay protection for those application that require the strict version of it, EIP-712 will need to be modified so wallet ensure correct values to protect their users.

messages don’t fall into corner cases, which would not be possible with a strictly fork block height-based mechanism.

As mentioned above, nothing prevent you from using a different mechanism for blockNumber. EIP1965 will still allow you to verify that a chainID was valid at a specific block no matter how you came up with that number.

To conclude, while I agree that not all points mentioned need to be added/ expanded in the rationale. the point about minority-led hard fork need to be addressed, (like EIP-1959 does for example)
I would also still strongly advise you to clarify better why a naive chainID equality check is dangerous.

That’s a good point. Contrary to @fubuloubu 's opinion, I think this is an important feature to have. Using a similar system than EIP1965 would allow transaction at fork boundary to be accepted as normal without downtime, an obviously desirable property form the user point of view. The users might also want to be sure their transactions do not get replayed on a particular side of the fork, even before the fork happen. Arround the time of the fork, the wallet could set the transaction chainId to be of the future chainID. Miner would leave it in the pool until the chainID change. For dealing with minority-led fork, the blockNumber at which the transaction was signed should also be included (like EIP-1965)

I’m very sorry if this has been discussed before: From a conceptual standpoint, I would prefer if this could be a precompiled contract, since it is external to actual computations in the EVM. Until we reduce the gas costs for calls to precompiled contracts (I would very much like to include this in the next hard fork, if possible), using an opcode is probably the only practical possibility.

If we “waste” a full opcode for this, the let’s at least make it extensible in some way: Make it take a parameter and return the Chain ID if the parameter is zero, the Hardfork number if it is one, and so on and also define reserved and non-reserved parameters.

Would that be a good suggestion?

Would like to make the point that tx.origin is also external to the actual computations in the EVM, and is obtained through the signed transaction in a very similar way that chain ID would be obtained. Chain ID was added to the transaction structure in EIP-155 as a protocol update, so in many ways this is just completing that work.

1 Like

There is alternative suggestions more along this path (EIP-1959, EIP-1965) if you are interested. I believe them not to be as flexible for the end-user as this proposal, but feel free to check those out. It has been suggested to make those pre-compiles due to their added complexity.

I decided to draft an example of a “Trustless Oracle Contract” for historical chain IDs, which is something that could very easily be implemented, audited, and leveraged as a standard for any potential use case requiring access to historical chain IDs, without the complexity of alternative proposals.

Check it out here:


Again would like to note that assuming what the end user might want to use current chain ID for may not be known in advance, and adding a more complicated API may otherwise prevent the implementation of specific use cases that a simpler API would allow through some smart contract magic.

1 Like

Wow we were thinking yesterday along the same lines then. I’ve draft this EIP which may be relevant: https://github.com/ethereum/EIPs/pull/2014

2 Likes

EIP is now implemented in aleth https://github.com/ethereum/aleth/pull/5696

1 Like

Was accepted for Istanbul as of the call today, however @karalabe made the suggestion that implementations should reference a hard-coded value of chain ID present in the client instead of the value provided by EIP-155 compatible transactions (which the reference implementation uses). This would reduce the corner case that EIP-155 technical allows not providing chain ID in a transaction, and also makes it more compatible with EIP-695 (adds Chain ID JSON-RPC call).

1 Like

@fubuloubu can you specify the value range of the chainid returned? Is it 64-bit, is it 256-bit, …?

It would be nice to know it as we’re trying to have explicit ranges, see EIP-1985.

256-bit. I didn’t realize it wasn’t in the proposal

1 Like

It’s perfect, thank you.

This EIP is well past last call date. Will the process to move from Last Call to Accepted be activated?

2 Likes

I have https://github.com/ethereum/EIPs/pull/1994 open for that.

With recommended inclusion into Istanbul, I am hoping we can move this to Accepted soon so that implementers may be able to reference this proposal in a Solidified state.

I’m still working on the reference implementation into Trinity, but the Aleth implementation is here: https://github.com/ethereum/aleth/pull/5696

To clear this part of the confusion: that was added after the adoption of EIP-155 in a hard fork as well as the update of YP.

I clearly remember the ACD calls (around 2016 or 2017) where a 1 byte value was agreed on.

While I think this is a clever idea, in the worst case it means the calculation for the transaction exceeds 256 bits.