Multi-Sig Concerns, Mangled Addresses, and the Dangers of Using Stake Keys in Your Cardano Project (Atomic Swap & TradingTent Bug)
Background
In Adamant’s last write-up we discussed a small flaw in the design of Cardano Plutus smart contracts used by projects such as MuesliSwap, jpg.store and SpaceBudz Market — we recommend reading that write-up before continuing as the topics discussed are relevant here.
Some key points from that write-up, as a refresher:
- Cardano addresses contain, among other things, two main pieces of information: the payment part and the delegation part.
- The payment part determines who can spend the funds which reside at the address. Only the holder of the cryptographic private key corresponding to the public key specified in the address can authorise the spending any of funds (UTxOs) held by the address.
- The delegation part specifies who can choose where the funds which reside at the address are staked and collect the staking rewards, again based on cryptographic keys.
- Anyone can create an address which specifies an arbitrary payment part or delegation part. If I send funds to an address which specifies my payment key in the payment part but a staking key you control in the delegation part then I still control of the funds because I have the power to spend them, but you can choose where to stake them.
- If the keys specified in the payment part and delegation part of an address do not belong to the same individual, such as scenario described above, the address known as a mangled address (sometimes also referred to as hybrid address or franken address).
- When we think about the “owner” of an address and the funds held by the address, it is the payment part of the address which is important. In this post when we say a person “controls” an address we mean they hold the private key corresponding to the public key specified in the payment part, because this person controls the funds at the address.
- Some popular Plutus scripts only checked the payment part of addresses. This resulted in situations such as: a malicious user purchases an NFT listed by Alice on a smart contract market but sends the payment to a mangled address for which Alice controls the payment part, but which specifies the buyer’s staking key in the delegation part, which allows the malicious buyer to keep staking control of the funds used in the payment. The smart contract authorises the purchase because it only checks the payment part of the address receiving the payment matches that of the seller.
In this write-up we again discuss mangled addresses, in particular how they break assumptions often made by developers building projects on Cardano when dealing with ownership of funds. We also discuss security considerations for projects dealing with multi-signature transactions, with an example bug in Atomic Swap, and how subtleties with mangled addresses allowed for a circumvention of TradingTent’s attempt to prevent a similar flaw. Both projects have since released patches addressing the issues.
Multi-Signature Transactions
For a Cardano transaction to be valid it must be signed by all the owners of any funds which are spent in the transaction. More technically this means the transaction must be signed by the private keys corresponding to the payment keys specified by the addresses which hold the UTxOs being used as inputs. You can think of someone signing a transaction which spends some of their funds as that individual agreeing to the details of the transaction, authorising those funds to be spent by the transaction.
For example, if a transaction uses two UTxOs as inputs, one residing at Address 1 and the other residing at Address 2, then the transaction must be signed by both the payment key specified by Address 1 and the payment key specified by Address 2.
If a transaction must be signed by more than one party it is known as a multi-signature transaction (more technically known as a multi-witness transaction, and not to be confused with multi-signature wallets). A number of Cardano projects utilise the properties of multi-signature transactions for a range of use-cases, the most notable examples being:
- NFT minting: typically the steps involved in minting an NFT might be as follows: the buyer starts their order to purchase an NFT, the buyer submits a transaction which sends the NFT minter some funds, once the minter receives the funds they submit a transaction sending the buyer the NFT. If the NFT was no longer available the minter submits a transaction refunding the buyer. Instead of needing to use two transactions and issuing refunds, by using multi-signature transactions the payment and minting of the NFT can be done in a single transaction and without the need for refunds. This works as follows: a transaction is built which sends some of the buyers funds to the minting party and also mints and sends the NFT to the buyer. The transaction is then signed by both the buyer, who is authorising his funds to be sent to the minter, and the minter, who is authorising the minting of the NFT. The transaction either succeeds and the NFT is minted or the transaction fails, maybe because the NFT was already minted by someone else, in which case no funds are exchanged meaning there is no need for a refund.
- No-escrow trades: if two parties wish to trade some assets they would usually use a trusted middle-man to collect the assets from both parties and then perform the swap. Using multi-signature transactions removes the need for a trusted middle-man, and the swap can safely be performed in a single transaction. The assets are either successfully swapped or no assets are exchanged. This works by having both parties include the assets they want to swap as inputs to the transaction, and having the transaction send each parties’ assets to the other party. The transaction is then signed by both parties in the trade, who give their permission for the transaction to spend their funds. If the transaction is accepted the assets are swapped, otherwise no funds are exchanged.
Atomic Swap
One project which facilitates no-escrow multi-sig trades is Atomic Swap. A swap is performed using a single multi-signature transaction as follows:
- Atomic Swap asks both parties in the trade, say Alice and Bob, to specify and tell the other party which of their assets they want to offer in the trade
- Knowing what the other party is offering, both parties build a transaction which uses Alice’s and Bob’s offered assets as inputs and sends them to the other party in the trade. Assets offered by Alice will be sent to Bob, and assets offered by Bob will be sent to Alice
- Alice or Bob signs the transaction using their payment key, then passes the signature to the other party who also signs the transaction before attaching both of the signatures to the transaction
- The transaction spends assets owned by Alice and Bob and now also contains the required signatures from both Alice and Bob, so the transaction is valid because both parties who control the funds being spent have agreed to the details of the transaction
- The transaction is submitted to the chain. If accepted by the ledger the swap will be performed, otherwise the assets held by both parties are unchanged
Wallet UTxO Spoofing
There is a notion in software security that you cannot trust the client; that is, we can’t trust that a client or user is behaving honestly and in a predictable way. In the context of Cardano, your web-app may connect to a client’s browser wallet extension and ask it for a list of all the assets or UTxOs which the wallet controls — your app cannot blindly trust that these UTxOs are really controlled by the wallet. A malicious user can modify their wallet or create a malicious wallet and respond with any information they choose. You must query the blockchain to verify the information provided by the client, i.e by checking which addresses control those UTxOs and requesting signatures from the payment keys in those addresses.
Knowing this, one critical security consideration for a tool utilising multi-signature transactions in ways similar to Atomic Swap is that when asked what funds Bob wants to spend in the transaction he must not be able to trick the application and specify funds which are owned by Alice (more precisely we cannot allow Bob to tell the app that a UTxO belongs to him when it actually belongs to Alice). This is super-important, because if we didn’t enforce this we could end up in the situation where:
- Atomic Swap asks Alice what funds she wants to send Bob
- Atomic Swap asks Bobs what funds he wants to send Alice, and Bob specifies some assets which are actually owned by Alice
- The Atomic Swap UI shows Bob is offering his own funds on his side of the trade, when actually those funds are owned by Alice
- Atomic Swap builds the transaction, and asks Alice and Bob to sign the transaction
- All the funds being spent by the transaction are owned by Alice, including those maliciously included by Bob, which means that as long as the transaction has a signature from Alice the chain will accept the transaction because the owner of the funds being used in the transaction has authorised it. If Alice signs the transaction and it is submitted, Bob will receive the assets Alice offered but Alice will not receive any funds, she will simply move funds around in her own wallet.
Atomic Swap lacked measures to prevent Bob from specifying funds (UTxOs) which were actually owned by Alice, which meant the above exploit was possible. A fix has since been released, more details on which can be found at the end of this write-up.
Note that most wallets (except certain implementations of single-address wallets such as Nami — more on this later) in this situation would reflect to Alice that she is not actually receiving the funds Bob added, but after being shown a UI on the website which displays everything is as expected it is not hard to imagine some users not properly double-checking the details of the transaction in the wallet prompt and therefore missing the discrepancy in the values being sent and received.
This also relates to multi-sig NFT minting. The minting back-end may ask the buyer to provide a list of UTxOs to use as payment for the NFT, but it must not allow the customer to specify and spend any UTxOs which are controlled by the policy key which is used to mint the NFTs.
Before we discuss a similar flaw in TradingTent, it is helpful to understand how wallets in the ecosystem currently work with regards to payment and delegation parts used in the addresses generated by the wallets.
Wallets, Payment Key Rotation, and Stake Keys
Currently the majority of Cardano wallets generate many addresses - perhaps a new address for each transaction. The wallet generates a new unique payment key for each new address, but uses a constant staking key for all of them. This means the first half of the addresses generated by a wallet (payment part) will always be different but the second part (delegation part) will remain the same.
This “rotation” of the payment key presents a problem for certain projects building on Cardano. If an application wants to query the blockchain to find all the funds controlled by a particular wallet then it needs to check the balance of every address used by the wallet, but how can the application discover all the addresses belonging to a wallet if each address has a different payment key, and each of those keys is unrelated to the others?
The solution which most projects have resorted to is choosing to identify a wallet by the constant staking key it uses, because this is the same in all the addresses generated by the wallet. The application can thus search the chain for all the addresses which use that particular stake key and it will find all the addresses which have been used by the wallet up to that point.
While this works well in typical circumstances, it is not a perfect solution. It is true that this solution will find all the addresses generated by the wallet, but it is not true that this will only find addresses generated by the wallet - in other words, it is not guaranteed that all addresses which specify the same stake key really belong to the same wallet (or more precisely, belong to the individual who owns the private key associated with the stake key). You may also find addresses which are controlled by other users.
This is due to mangled addresses, which we discussed in our previous write-up. It is simple for someone to send their funds to an address which specifies their payment key but someone else's staking key, and they can do so while maintaining control of the funds. When a project searches the chain for all the addresses which use a particular stake key, it might also find mangled addresses which specify that stake key but are not controlled by the individual which actually controls that stake key.
The payment part of the address is the important part - this is the part that specifies who the owner of the funds at the address is, but because apps cannot find all the different payment keys used by a wallet they resort to using the stake key.
Let’s take a look at some examples where this can be seen in projects, and how mangled addresses break their design:
- cexplorer.io, a blockchain explorer, displays info about the owners of a particular asset and how many of the asset the owner holds. They do this by treating each stake key as an individual/wallet, and then totalling the amount of the assets held by addresses which use that stake key. This is flawed because in the situation where one NFT is at an address controlled by Alice and another NFT is at address controlled by Bob but which specifies Alice’s staking key in the delegation part, then Bob’s NFT will be shown as belonging to Alice.
- on pool.pm, which is often used to view NFTs owned by a particular individual, when you search for an address it will pull out the stake key and display all assets owned by addresses which use that stake key. This is flawed in the same way as above, when inspecting what NFTs Alice owns you will also see Bob’s NFTs, and when inspecting Bob’s NFTs you will also see Alice’s.
- NMKR and a number of NFT projects have suffered from issues by relying on stake keys for purposes like whitelists. For example, maybe we want to only allow users who own a particular “whitelist NFT” to be able to purchase a new NFT. To do this we take the address of the buyer, and check if they have the required “whitelist NFT” in their wallet. We may perform this check by finding all the assets at addresses which use the same stake key as the buyers address and then check if the required NFT is contained in those assets, under the assumption all addresses with the same stake key belong to the same wallet. This is flawed because a malicious buyer can find an address which owns the whitelist NFT, then craft a new address which they control (uses their payment key) but which uses the stake key from the address which is holding the whitelist NFT. Then, if the malicious user uses the new mangled address to purchase the new NFT we will pull the stake key, see that the NFT required to mint does reside at an address which uses that stake key and assume it is controlled by the malicious buyer’s wallet, and thus allow the mint without the malicious buyer actually owning one of the required NFTs.
In the last case, a near-perfect solution is to send the minted NFT to the first address which was seen on chain that uses the stake key specified by the address which holds the NFT required to mint, regardless of the address which makes the payment. The logic here is that the first address on chain which specified that stake key is probably controlled by the true owner of the stake key because there was no opportunity for anyone to have copied it into a mangled address as it is the first occurrence on the chain. I say near-perfect because:
- an attacker could monitor the mempool, detect a transaction to a stake key which has never been seen on-chain before and front-run the transaction by sending funds to a mangled address which specifies the same stake key but with the attacker’s payment key. This would mean the first address on seen on-chain specifying the victim’s stake key will actually belong to the attacker. This is unlikely, but technically feasible none-the-less. Perhaps you could avoid this by sending the minted NFT to the address which registered the stake key instead of the first address seen simply specifying the key.
- mangled addresses break this design. Using the flaw described in our last write-up or otherwise, a malicious user could send someone the NFT required to mint but send it to a mangled address which specifies the malicious user’s stake key. This means that if that whitelist NFT is used in a purchase, the purchased NFT will be sent to the malicious users address instead of the buyer (who assumes the required whitelist NFT is linked to their own address) because the dApp sends the NFT to the first address which uses the stake key specified in the mangled address which holds the required NFT —which is the malicious user’s stake key, and thus the malicious user receives the minted NFT.
The only way of knowing if someone is the owner of some funds is to have them spend the funds or by having them provide a signature with the payment key which controls the funds. If two addresses specify the same payment key then they are controlled by the same person. If we want to confirm someone is the owner of an NFT, we either need a signature from the payment key specified in the address which holds the NFT or we must force the NFT to be sent to a particular address.
TradingTent Mangled Address Bug
The subtleties described above resulted in bug in TradingTent, under certain conditions. TradingTent is a website similar to AtomicSwap which allows for two parties to perform a trade of their assets in a single transaction using the concept of multi-signature transactions. The difference is that TradingTent handles building the swap transaction on their website’s backend, where as AtomicSwap is fully peer-to-peer so both parties build the transaction locally. This has benefits for TradingTent as the backend can query the chain to perform checks on the assets offered by two parties in the trade.
In order to prevent the flaw faced by AtomicSwap, where by one party can offer funds in the trade which are owned by the other party in the trade, TradingTent requires that Bob provides a signature signed with his stake key before allowing him to add any assets to the trade, and then enforces that Bob can only add assets which reside at addresses which use that stake key. The signature proves that Bob is the real owner of the stake key, which prevents him from specify someone else’s stake key in his address in order to use their assets in his offer, and then we only let Bob use funds at addresses which specify that stake key under the assumption all these addresses are owned by Bob’s wallet.
This solution was almost correct, the signature was a step in the right direction, but unfortunately not perfect. Consider the following scenario:
- Alice has a SpaceBud listed for 1000 ADA on the SpaceBudz Market
- Bob abuses the flaw described in our last write-up, purchasing the SpaceBud but sending the 1000 ADA to a mangled address for which the payment part specifies Alice’s payment key but the delegation part specifies Bob’s stake key
- Bob starts a trade with Alice on TradingTent
- Bob provides a signature to prove he is the owner of the stake key specified in his address
- After verifying Bob is the owner of the stake key TradingTent allows Bob to add any funds which reside at addresses which use his stake key, because it assumes that these are funds controlled by Bob’s wallet — and here’s the bug: the payment that Bob sent when purchasing the SpaceBud was sent to an address controlled by Alice, but which specified Bob’s stake key, so under TradingTent’s logic Bob is able to add the funds he sent to Alice to his side of the trade, even though those funds belong to Alice
- Bob adds the funds he paid to Alice to his side of the trade, even though they belong to Alice
- TradingTent UI displays Bob of offering the 1000 ADA, when actually this ADA resides at an address belonging to Alice (but which specifies Bob’s stake key in the delegation part)
- TradingTent has Bob and Alice sign the transaction before submitting it
- Alice’s offer is sent to Bob but the 1000 ADA Bob offered is simply an internal transfer between two of Alice’s addresses, and so Alice gains nothing despite the TradingTent UI saying she would gain 1000 ADA
The flaw results from the same faulty assumption we discussed earlier that all funds residing at addresses which specify the same stake key are controlled by one person, the owner of that stake key. This allows one party in the trade to include funds which reside at a mangled address which specifies the other party’s payment key but their own stake key - and while this situation may seem unlikely at first, remember that our last write-up detailed how:
- An attacker could buy an NFT on jpg.store but send the payment to a mangled address (patched in the new version of the contracts), meaning the exploit described would then be possible when trading against the seller of the NFT
- You can buy a SpaceBud on the SpaceBud Market but send the payment to a mangled address, meaning the exploit described would then be possible when trading against the seller of the SpaceBud
- You can perform the role of a matchmaker for MuesliSwap, but send the funds users traded to mangled addresses, meaning the exploit described would then be possible against the users on both sides of the order-book trade
The malicious party may send the victim party funds for any reason before the trade, as long as the funds are sent to a mangled address it can be used in this exploit.
Nami Wallet and Mangled Addresses
As previously mentioned, most wallets would show the discrepancy in the funds being exchanged to the victim when they are prompted to sign the malicious multi-sig transaction — but this is not true for Nami. Nami is a single-address wallet, which means it only tracks one specific address: a single payment key, staking key pair. This means Nami will not detect funds which reside at mangled addresses because the staking key is different to the one in the address Nami tracks, even though the wallet technically controls the funds because it controls the relevant payment key. Because of this, when the malicious party offers the victim’s funds (which reside at a mangled address) on their side of the trade Nami will not realise that the funds offered by the malicious party actually belong to the victim, and will show an increase in balance despite the funds just being moved internally between addresses the victim controls. Nami doesn’t realise the funds are being sent to the wallet from another address that it technically controls, so it shows the balance of the wallet as increasing. Such a situation would mean the victim would have no reason not to sign the transaction.
Nami uses Blockfrost to query the chain and it did not appear that Blockfrost had functionality to find all funds controlled by a specific payment key, which would allow Nami to detect funds at mangled addresses — it turns out that this functionality does exist but is not included in the documentation. Blockfrost will be updating their API docs to detail this functionality, and we have worked with Nami to add support for mangled addresses which should be available post-Vasil hard-fork.
Fixes and Protections
After receiving the bug report TradingTent swiftly began working on a patch and awarded Adamant with a bug bounty. A perfect fix to the UTxO spoofing issue faced by multi-sig projects requires querying the chain to check who controls each UTxO specified in the trade. For example, Bob chooses a UTxO to include in the swap and the backend checks that this UTxO exists on-chain and finds the payment key which controls it, then the backend requires that Bob individually provides a signature of the transaction using this specific payment key. This prevents Bob from choosing a UTxO owned by Alice because doing so would require Bob to provide a signature created with Alice’s private payment key, which he does not have.
Atomic Swap is fully peer-to-peer which means there is no backend which can query the chain and ensure both parties in the trade are being honest, instead the code for building the transaction runs locally on the machines of both parties in the trade. For this reason Atomic Swap opted for a different solution: Alice’s wallet knows the list of UTxOs which she controls which means she can check that none of the UTxOs included in the swap by Bob are found in her own wallet. This is a simple but effective solution which is almost perfect, but there is a technically a time window where Bob could include pending UTxOs controlled by Alice which Alice’s wallet does not yet detect — specifically UTxOs created by transactions which have been accepted by mempools but not yet included in a block.
If your project is dealing with multi-signature transactions, it should verify any information supplied by the clients instead of simply relying on the ledger rules.
Though the wallet responds with a list of CSL’s TransactionUnspentOutputs, you cannot trust the address and value in the TransactionOutput field, as this can be changed by the attacker. Hence it would not suffice to perform checks on the addresses contained in the TransactionUnspentOutputs received from the wallet. You must take the identifier of the UTxO from the TransactionInput field and use it to query the chain to discover if the UTxO exists and if so what the associated address and value is. To confirm if a user controls this UTxO request them to provide a signature of some data using the payment key which controls the UTxO.
Thanks to TradingTent and Atomic Swap for their cooperation regarding this matter, and to TradingTent for also rewarding Adamant with a bug bounty. If you want to trade assets I can recommend both of them!
Harper (@hrpr) for Adamant (@4d4m4n7), 22nd July 22. Harper is a recent graduate from the University of Edinburgh with an interest in security engineering, and has spent 3 years with Bcryptic performing continuous security audits of the protocol-level Cardano ledger code on behalf of IOG.
If you found this article helpful consider following for more on Cardano security and other related topics, and share with others who might find value in the topics discussed here. Also, be sure to follow us on Twitter to be the first to hear about Adamant’s upcoming project!