ECIP: 1011
Title: Modification of Ethereum Classic (ETC) Transaction Signing Process to Prevent Replay Attacks
Author: Avtar Sehra <[email protected]>
Status: Draft
Type: Standard
Created: 2016-10-02
This ECIP describes the process of modifying the ETC process used for signing and validating transactions for inclusion into the ETC blockchain. The proposed approach is akin to the BTC signing process, and will ensure Ethereum Hard Fork Chain (ETH) transactions can not be replayed on the ETC network.
Other methods proposed for the Replay Attack fix require either modification of key symbolic values (e.g. the account nonce, which represents the number of transactions sent from the sender's address) or increasing the transaction sizes by adding an extra field to the sent transaction (e.g. an arbitrary reference block hash). Such modifications would impact backward compatibility of transaction structures. With the approach proposed here there is no change in the structure of the transaction and or the intrinsic values, however there would be a modification to the transaction signing and validation process. This difference would take the form of adding an extra arbitrary value to the transaction prior to performing the transaction hash for signing. This would result in a unique message and thus signature that would differ from that of the ETH chain. Such a process will be cryptographically secure and ensure that all transactions broadcast from the ETH network cannot be accepted in the ETC network and vice-versa.
In the Bitcoin protocol the scriptPubKey script is copied from the source transaction into the spending transaction (i.e. the transaction that is being signed) before calculating the signature. Then the signature script, scriptSig, is embedded in the transaction. Using the previous transaction's scriptPubKey during the signing process is for historical reasons rather than any logical ones.
In the Ethereum protocol a transaction (formally T
) is a single cryptographically-signed instruction constructed by an actor externally to the scope of the Ethereum network. Both message call and contract creation transactions specify a number of common felds: T=(T_n, T_p, T_g, T_t, T_v, T_(i or d),T_w, T_r, T_s)
, where T_w, T_r, T_s
are the signature components for the elliptic curve digital signature algorithm utilized in the Ethereum protocol.
The current signing process is as follows:
- The message to be signed is constructed:
L_s(T)=(T_n, T_p, T_g, T_t, T_v, T_(i or d))
whereT_i
contains the EVM code ifT_t=null
(no target address) orT_d
is an unlimited size byte array ifT_t<>null
(has target address) - The Keccak Hash is than calculated:
h(T)=KEC(L_s(T))
- The signature process then generates:
(T_w, T_r, T_s)=ECDSASIGN(h(T),p_r)
, wherep_r
is the senders private key. - We can then define the sender function
S
(giving the sender's address) for the transaction as:S(T)=B_(96...255) (KEC(ECDSARECOVER(h(T), T_w, T_r, T_s)))
The modified process proposed would be to insert an extra step between points 1 and 2, which appends an arbitrary value, T_a, to the transaction prior to signing and or validating. However, unlike in BTC where the value is taken from the source transaction the suggestion in this approach is to utilse a block header hash i.e. T_a = Block Header Hash
. So the process becomes:
- The message to be signed is constructed as before:
L_s(T)=(T_n, T_p, T_g, T_t, T_v, T_(i or d))
- Append the Tx Hash of
Netc
i.e. given byT_a
:M_s(T)=(T_n, T_p, T_g, T_t, T_v, T_(i or d), T_a)
- The Keccak Hash is than calculated:
h(T)=KEC(M_s(T))
- The signature process then generates:
(T_w, T_r, T_s)=ECDSASIGN(h(T),p_r)
- We can then define the sender function S (giving the sender's address) for the transaction as:
S(T)=B_(96...255) (KEC(ECDSARECOVER(h(T), T_w, T_r, T_s)))
In this way no extra information needs to be added to the transactions to increase their size, the transaction structures would remain backwards compatible and all key symbolic fields would remain unchanged.
Even though this is a much simpler change for implementation compared to other suggestions and does not require changes to the standard transaction structures, this does require a change in the fundamental process related to the Ethereum Classic protocol. As a result it is a higher risk change and would require much deeper testing to ensure transactions are not lost through unforseen errors in the signing process.
The choice of signing variable will be a fixed value, but support for offline transactions using hardware wallets would also need to be included. Therefore the choice of the signing varibale can be:
- 0 (null) to support offline transactions
- Block hash of the most recent fork, T_a, where T_a is the hash of the Genesis block if no forks have occurred.
For example the hash of block Netc=3,000,000
, which is exepcted to be the Hard Forking block for delaying the Difficulty Bomb.
The value of T_a can be chosen in an arbitrary way if it is constant e.g. the block header hash of a specific block can be chosen for simplicity to be Netc = 1920000
(the point of the ethereum fork) or Netc = 3,000,000
(as noted above). This value can be easily updated in future forks by using a new reference block. This process can also be easily managed by light clients, even with end users making an update to the reference block variable to match the relevant value chosen by the network (if the option is made available to end users).
An alternative method would be to utilize a dynamic value for T_a
. One possibility would be to use the block header hash of a reference block that is updated after a lead time of T_l
or after N_l lead blocks
. If we say 1 epoch
is 1024 blocks
or 4 hours
, then we can use a lead time of T_l = 6 epochs
or N_l = 6,144 blocks
. Therefore the reference block is updated every 24 hours
(every 6 epochs
).
For example, starting at Netc = 3,000,000
we update T_a
with the new reference value after each N_l
blocks. If BH(N)
is the function for the block header hash for the Nth
block, and we choose to reference a lagging block we have:
t0 : T_a = BH[Netc - N_l]
t1 : T_a = BH[Netc - N_l + N_l]
t2 : T_a = BH[Netc - N_l + N_l + N_l]
...
tn : T_a = BH[Netc - N_l + N_l * n]
or
tn : T_a = BH[Netc + N_l * (n - 1)]
where
n = RoundDown[ (N_c - Netc)/N_l ]
N_c
is the most recent block number.
In this way validation can then take place using a pool of 1 week of values: t0, t1, t2 ... t7
, which implies at most 7 checks would be required to be performed for transactions that are delayed by up to 7 days. A limit should be in place to identify how far checks should go.
This approach would ensure that for any future forks the replay attack is resolved after a time lag of 6 epochs
(or 24 hours ~ 6,144 blocks
).
In this dynamic approach, an option could be used where T_a
is also an arbitrary value 0
that is used by light clients or for constructing off-line transactions. However, this means that light clients or offline transaction construction is suciptible to replay attacks.
The dynamic signing variable is only highlighted here for completeness and is not being considered for Replay Attack Fix due to potential concerns related to enforcing transaction expiry times at the protocol level and the added cost of computation required for long expiry times (validation period above).