By: Jesse Abramowitz

A few weeks ago an interesting attack was taking place on the Polkadot test network. The attack was so interesting and fascinatingly unique to Polkadot that I had to write about it. Before you panic, take comfort in the fact that it has been handled and will not be an issue anymore.

Background

I am going to tell this like a chronological story, although I do find stories are the most interesting when you start at the end and jump to the beginning (periodically checking into the end every now and again).

At BlockX Labs we have been building a Universal Faucet that supports Polkadot. Since DOTs can influence the code of the blockchain due to Polkadot’s governance, they don’t give out DOTs willy nilly. Which is cool, but it means that our faucet needs to be refilled every now and again.

We also do keep a close eye on how much is being asked for. We noticed that we were being drained quickly, and that the transactions weren’t being logged in the backend. It was driving us nuts. Anyways, we found a GitHub issue which explained it all.

To understand the fix we need to understand the issue as well as certain very cool features of Polkadot.

Replay Attacks and Nonces

A Transaction object has a few parameters that must be included.

tx = { To, Value, Gas, gasPrice, Data (optional),



ChainID }

Pretty much you have to say what you are sending, who you are sending to, and sign it with your private key.

Then the “blockchain” will do a signature recovery to make sure the account has funds, and send it out.

But wait — This isn’t secure! In this model the transaction can be replayed:

Source: https://medium.com/cypher-core/replay-attack-vulnerability-in-ethereum-smart-contracts-introduced-by-transferproxy-124bf3694e25

If Alice signs a message to Bob saying she will pay him $20 then broadcasts it, nothing is stopping Bob from grabbing that signed message and replaying it at a later time.

This is why we have something called a nonce. Nonces pretty much act as a counter on a wallet, forcing each transaction to have an order (as in 1 has to be sent, followed by 2, etc.). So when Bob tries to “replay” Alice’s transaction, the transaction fails because transaction 1 has already been used. Changing the Nonce requires resigning the message with Alice’s private key, and thus we are secure again.

State Trie and Polkadot’s Existentialism

An account-based Blockchain has something called a global state trie. This holds information that sits at an address (code as well as other things for smart contracts — for externally owned accounts this would be things like nonce and balance).

It has been known for a while that Ethereum’s state trie is getting big and there is literally no pruning going on. There’s no way to remove old data and accounts, Meaning the problem will only get worse. Some ideas like state rent are now being tossed around, but it is clear some form of pruning should take place.

More on that point: I don’t know who said that data should be immortal, but if you subscribe to that belief I have to ask what the cost of data immortality is.

Polkadot has something called an existential amount. This is an amount that is considered so low that if someone lost it the effect would be negligible, so once an account balance drops below that amount it can be pruned from the state trie. That amount is 0.1 DOTs.

Entering the State Trie

We talked alot about exiting the state trie, but how do we enter it? Easy — just pay an amount greater than 0.1 DOTs into an account… simple. What this means though, is that an account can leave the state trie and then re-enter it again at a later date… with a nonce of 0.

That leaves the account vulnerable to replay attacks. In this scenario, previous transactions could be re-sent (up until the nonce where it exited the state trie).

Solution

The solution is actually pretty simple once you really understand the problem. You creating a mortal transaction, meaning you state that your transaction is only valid between a certain amount of blocks. Sign that message, and when someone tries to replay it the transaction will fail.

After understanding the problem, I made a pull request to Polkadot.js. It was cleaned up a little by Jaco, and now directions for adding this to a transaction can be found in the documentation here.

I hope you found this explanation of replay attacks and transaction mortality helpful. At BlockX Labs we’re proud to contribute to Polkadot in ways like this. Feel free to reach out to me at any time on twitter.