JAN 7, 2024
Permissionless Merkle Tree Attack
by Vlad, Access Protocol
Why permissionless cNFT minting in your Solana on-chain program might not be a good idea.
Vlad is a Fullstack Software Engineer at Access Protocol. You can reach out to him on X (@mmatdev) or via e-mail at email@example.com.
In the previous articles (here and here) and the repo here I have described how you can create a decentralized mechanism in your on-chain program for minting cNFTs to users' wallets. In the described solution anyone can call your on-chain program and thereby mint a cNFT into their wallet. In this article I would like to outline why this is not a good idea without introducing additional checks.
As you might already know, cNFT data are not stored directly on-chain, but on the ledger (you can think about this as a log of the on-chain transaction history). The only thing stored directly on the chain, that ensures that the cNFTs cannot be tampered with, is a part of a Merkle tree with the leaves equal to the hashes of the cNFT data. This is very cost-efficient and makes the cNFTs as cheap as they are.
The attack vector
In the on-chain program in the previously mentioned repo the Merkle tree account can be created by anyone. However, we can quite safely assume that the on-chain program author will be the one creating new trees in case the old ones get filled up, to ensure that the on-chain program doesn't stop working. This opens up an attack vector where anyone with enough funds can fill up your Merkle tree and make you pay for the creation of a new one over and over.
Let's do some simple calculation to understand the leverage that the attacker might have on you: Let's assume that your program works with trees of max depth 14 and canopy depth 11:
- The tree creation costs ~1.134 SOL.
- This tree can store up to 16,384 cNFTs
- It costs 5000 lamports to mint a new cNFT
Therefore, it costs 0.08192 SOL to fill-up the whole tree.
As you can see, if you keep the minting fully open to anyone, the attacker can easily fill-up your Merkle trees for less than 1/10 of its creation cost - thereby making you spend a lot of funds to keep your program working. Even though the attacker is not directly stealing any funds in this scenario, they can make your program unusable and start blackmailing you.
Unfortunately, there is no silver bullet here, you need to decide what the purpose of your program is. Then you should adjust it so that the attacker is spending more than you - making the attack economically infeasible.
Some simple and quite general solutions are:
- Add an adjustable fee to the mint operation to cover the price of the tree creation. It can be transferred to the account that pays for the tree creation.
- Burn the fee instead of sending it to a specific wallet. This is a little more decentralized approach.
- If you want to allow only one mint per wallet, you can initialize a PDA derived from the wallet address. This solves the fee as well because it raises the mint cost to >0.001 SOL.
- If the cNFT works as a reward for some other more expensive on-chain operation, add a check into the mint instruction that this operation has been carried out. Only allow minting if the condition is fulfilled.
Solution when using cNFTs as a wallet
Any PDA on Solana can be used as a wallet. Therefore, we can use PDAs derived from the cNFTs asset ID to CPI into any other program. One specific use-case of this will be found in Access Protocol V2 where there is a possibility to lock your funds into a transferable cNFT instead of binding them directly to your wallet.
If you have a similar goal, you can achieve this by splitting your mint process into three parts:
This way you keep the price for minting the cNFT at 5000 lamports and ensure that you make the possible attacker pay elsewhere, e.g. in case of Access Protocol V2 by locking ACS tokens into a pool.
Keeping Merkle tree in your on-chain program purely permissionless is dangerous. It only requires one individual with 10 times less funds than you have to ensure that your program will never be usable again.
However, this can be mitigated quite easily without hurting your users.