You’ll note that the shielding flow in the app has been updated for better UX and security.
In this topic, I’d like to explain the historical shielding process for ETH/ERC20 tokens: what the original design and drawbacks were, the new improvements, and what we’re ultimately working towards. Additionally, we believe that an exploit was carried out last week that took advantage of a user who had a timed out shield request under the original design. This post will provide contextual information about the mechanism, detail the exploit, and explain the improvements - which are now live after the maintenance done last week.
Drawbacks of the original design
The original design was not ideal since it relied too much on the 2 hour window.
Specifically, there was a number of temporary addresses used for shielding requests, assigned in a round-robin fashion. These temporary addresses were in part designed to aid the user; each contained funds to cover the smart contract costs of the shielding action. However, the design came with its own set of limitations. Each shielding request could only own a temporary address within a specific time window (2 hours in this case). A temporary address could then be assigned to an incoming shielding request after the time window closed, or after the shielding process completed within the time window. At the beginning of a shield, the system would snapshot the existing amount held by the temporary address and watch out for an incoming transaction (its amount) sent by a user to the temporary address, in order to calculate the shielding amount.
That’s why we have always strongly suggested sending from a personal address that a user has direct control over, especially now that so many crypto platforms are experiencing delays due to high traffic. In the app, users had to confirm that they were sending from a personal address in order to continue, though there was no way to force them to do so. While this rather convoluted and inflexible process was and is overdue for improvements, instructions given on the shielding screen, when followed, would ensure the overall goal was successful.
If a user completed the request within the 2 hour window, funds would arrive safely because the beneficiary address would not yet be rotated and assigned to another requester (this beneficiary address is one of the set of temporary addresses mentioned above).
Unfortunately, there was recently a user who fell outside the window by several hours. Prior to this point, even timed out ETH/ERC20 shielding requests had been successfully filled before their temporary addresses were assigned to new requests. Via snapshotting as detailed above, and with help from vigilant users who reported any delays quickly, timed out shields could be retried with new temporary addresses to continue the original shielding process. This time, however, it is highly probable that an attack occurred which caused the user’s funds to be received by someone else.
Luck or malicious attack?
After an intensive investigation of the flow of funds as well as the behavior of the beneficiary account (a.k.a attacker’s account), we believe that it was likely a deliberate attack.
The attacker continually created shielding requests to obtain a large number of temporary addresses and assign them to his fake requests.
He then monitored all incoming pending transactions to these addresses over the Ethereum network.
Once he detected one, he kept creating shield requests continually until he obtained a shielding address corresponding to the detected pending transaction. That also means the pending transaction corresponded to a timed out shield request, and the shielding address was available for rotation. At that point, when the funds arrived, the shield request of the attacker processed. In other words, the attacker took the funds of the user who had the timed out shielding request.
As you may know, we paused the ETH/ERC20 shield feature for a week to work on improvements. It prevents similar attacks from occurring in the future, works towards a greater degree of decentralization, and more generally hopes to improve the overall shielding experience.
In this design, each Incognito account has a dedicated shielding address for all its shields. Funds may be sent to this address at any time without worrying about the address timing out. Once the funds land in the shielding address, a background job will kick in and start processing the shielding request.
This means that the shielding fees will be paid by the user; the system will deduct a part of the shielded funds to pay smart contract interaction fees. Depending on the gas price at the time of shielding, the system will calculate the fees in ETH. If ERC20 tokens are being shielded, e.g. DAI, the system will convert the ETH fee to DAI, then deduct the fees in DAI from the original funds.
This is now live. The latest app version is 4.3.8, please update your app in case you don’t have it yet. If you have any questions about the new shielding flow, feel free to comment below this topic or reach out to @support.
A longer-term decentralized solution
The medium-term design may help prevent attacks like the one described above, but a system that has any centralized elements always carries risk. A fully decentralized solution necessitates the complete removal of centralized parties (i.e., the temporary addresses). Work is already being done to this end, with more details to follow.
In the near future, a user will be able to shield ETH/ERC20 tokens right from his personal wallet, e.g. Metamask, Trust Wallet, etc. – and will no longer need to send funds to a temporary address managed by the Incognito team. A detailed timeline and specification will be published on the forum soon. The code will be completely open source.