By: Kong@ Slow Mist Security Team
background
On June 9th, Optimism and Wintermute both issued announcements, disclosing to the community an incident in which 20 million OP tokens were lost. Optimism commissioned Wintermute to provide liquidity services for OP in the secondary market, and will provide 20 million OP tokens to Wintermute. In order to receive this token, Wintermute gave Optimism a multi-signature address. After Optimism sent two transactions in the test and Wintermute confirmed that they were correct, Optimism transferred 20 million OP to this address. After Optimism transferred the tokens, Wintermute found that they had no way to control these tokens, because the multi-signature addresses they provided were only deployed on the Ethereum mainnet for the time being, and had not yet been deployed to the Optimism network. Wintermute immediately launched a remedial operation, but an attacker has noticed this vulnerability and deployed multi-signature to the address of the Optimism network before Wintermute, successfully controlling the 20 million tokens. So the question is, why is there such a loophole?
Pre-knowledge
First of all, it is necessary to determine whether the transaction signature conforms to the [EIP155] standard. The signature conforming to the [EIP155] standard will hash 9 RLP encoding elements (nonce, gasprice, gas, to, value, data, chainid, 0, 0), where The chainid is included, so the v-value of the [EIP155]-compliant signature is {0,1} + chainid * 2 + 35. For the signature that does not conform to the [EIP155] standard, it only hashes 6 elements (nonce, gasprice, gas, to, value, data), so the value of v after signing is {0,1} + 27. Different chains will define different chainids, and different chainids will get different v values. According to ECDSA, we know that when the value of v is different, even if the value of r and s is the same, the public key restored by the signature is also different. Therefore, transactions that meet the [EIP155] standard cannot be successfully replayed on other chains.
It is worth mentioning that [EIP2718] introduced a new transaction format 0x02 || RLP([chain_id, nonce, max_priority_fee_per_gas, max_fee_per_gas, gas_limit, destination, amount, data, access_list, signature_y_parity, signature_r, signature_s]), the chainid is encoded separately and not included in the signature v value. The signature v value is only used as a simple parity bit, so the v value obtained by the current transaction signature becomes 0 or 1.
transaction replay
After we understand the above transaction signature structure, we can clearly know that the signature v value of 27 or 28 can be replayed on different chains. So how to replay on different chains? This is no different from sending a transaction, we only need to send the original transaction content on other chains.
Take the theft of 20 million OP tokens in Wintermute as an example, in which the attacker replayed the transaction in which Gnosis Safe deployed the Factory contract. Here we try to replay Gnosis Safe Deployer 3 transactions with a nonce of 3.
A simpler method is to first obtain the original transaction through Etherscan:
Then directly through Optimistic's eth_sendRawTransaction [RPC]
(https://eth.wiki/json-rpc/API) interface to send.
If the original transaction content cannot be obtained directly, we can first pass eth_getTransactionByHash
[RPC](https://eth.wiki/json-rpc/API) interface to obtain transaction content.
Then RLP encodes the transaction content to get the original transaction content:
Then pass Optimistic's eth_sendRawTransaction [RPC]
(https://eth.wiki/json-rpc/API) interface to send.
Reference :
https://eips.ethereum.org/EIPS/eip-155
https://eips.ethereum.org/EIPS/eip-2718
https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm
https://github.com/ethereum/go-ethereum/blob/master/core/types/transaction_signing.go