When CryptoKitties launched back in late 2017, I bought a handful of kitties (seven or eight, including a rare Generation 0 kitty — our most beloved) with an address that we used for development and testing purposes. In hindsight, I don’t know why I used this address — I might have just been scrambling to get my hands on some kitties while the hype was real.
Months later, I decided to move those CryptoKitties to a newer address.
The original address held all the CryptoKitty NFTs but there wasn’t any ETH to transfer the kitties out. So, I sent .1 ETH to the address for the purpose of using it as gas to get the kitties out, assuming everything would work as expected.
Narrator: It didn’t work as expected.
This was a mistake. The ETH was immediately funneled out to another address — clearly, this tester address had been compromised at some point between me buying CryptoKitties and my trying to move the CryptoKitties, and someone had a script that automatically sent out any ETH that was deposited to it.
Thinking it might be a fluke, I tried sending another transaction of .01 (or less) to see if it would get sent out again. It did.
I had no idea what to do.
I decided to take this issue all the way up to the top; to someone who could either empathize and laugh at the situation, figure out how to resolve it, or all of the above. I took it to Taylor.
“Taylor, I need help. The CryptoKitties are trapped in a compromised wallet and I can’t get them out because the hacker just takes the gas immediately.”
Taylor took a look for herself and tried sending a small amount of ETH just to see the script in action.
The problem was that this script was running automatically. Each time it detected ETH coming into the account, it immediately sent the all the ETH out. Without ETH, we couldn’t release our CryptoKitties from their prison, as we couldn’t pay for gas.
The only way to rescue the CryptoKitties would be to beat the script so that the [transaction to send the kitty out] was mined before the [transaction that drained the account of ETH].
There are two ways to have one transaction be mined before another transaction if two transactions are sent around the same time, with the same nonce:
1. Broadcast it before the other transaction.
2. Use a higher gas price.
In this case, in order to beat the script time-wise, we would likely have to run our own script. Then we could adjust the gas price if needed.
But we didn’t really want to waste resources writing a script to rescue the kitties. We were already debating if we should just call it a loss as it was.
As Taylor was telling me about this, she kept referring to the nonce. A nonce is a sequential number that represents the number of transactions the sender account has made on the network. Each and every account’s transactions start with a nonce of 0 and goes up sequentially. You cannot send two transactions from one account with the same nonce. Learn more about nonce here.
The hacker’s script grabs the amount of ETH in the account, adjusts for the transaction fee, grabs the nonce, and broadcasts the transaction. But what if instead of beating the time or the gas, we could use the nonce somehow? 🤔
Taylor created a number of transactions but didn’t broadcast them. These transactions fell into two categories.
1. Sending ETH from her account to the compromised account (“CryptoKittyPrison”).
2. Sending kitties from their prison to a new, secure home (“SafeAccount”).
She incremented the nonce for each of these transactions so that instead of having to re-enter the transaction information in order to send, she could just broadcast the transaction instantly.
She created a pile of signed, unsent transactions like this:
- Send 0.004 ETH to CryptoKittyPrison. Nonce 100.
- Send 0.004 ETH to CryptoKittyPrison. Nonce 101.
- Send 0.004 ETH to CryptoKittyPrison. Nonce 102.
- Send 0.004 ETH to CryptoKittyPrison. Nonce 103.
She also had a pile of signed, unsent transactions that looked like this:
- Send CryptoKitty 1 to SafeAccount. Nonce 850.
- Send CryptoKitty 1 to SafeAccount. Nonce 851.
- Send CryptoKitty 2 to SafeAccount. Nonce 852.
- Send CryptoKitty 2 to SafeAccount. Nonce 853.
- Send CryptoKitty 3 to SafeAccount. Nonce 854.
- Send CryptoKitty 3 to SafeAccount. Nonce 855.
She would, as fast as she could, copy the signed transaction, paste it into one of the windows, click the send button, go to the next tab, and repeat.
We assumed the first ETH we sent in would be sent out by the phisher’s script. But when the second…and third…and fourth transaction got sent in, what would happen? If just one of our kitties got out, would the script be “behind” a nonce and allow another kitty out?
The answer was yes.
It worked! A few transactions worth of ETH would go into the CryptoKittyPrison, and the script would only take one or two… allowing one or two of the [CryptoKitty to Safe Account] transactions to go through. We were rescuing the kitties!!!
A few more rinse and repeats of this cycle, and we eventually got them all out.
The day was saved.
If you want to see how it all went down, all the transactions are visible thanks to the magic of blockchain. Here’s an annotated screenshot of the transaction history from Etherscan. You can see where I first realized the address was compromised, us testing out more transactions, and finally figuring out how to rescue the first CryptoKitty.
After all of this, we got a lot better and started getting two kitties out at a time. To see the full TX history of that address and the saga of the CryptoKitty rescue, visit Etherscan.
Huge thank you to Taylor for her blockchain prowess and devising a plan of attack to save the CryptoKitties.