Polygon Warrior Polygon Security Dialysis: How to Ensure Cross-Chain Security and Stability?

Author: Chengdu Lian’an
Original: “Research on Cross-Chain Bridge Security (3)” | Polygon Warrior Safe Dialysis, How to Prevent the Opening of “Pandora’s Box”?

Welcome to the series of articles on “Cross-chain Bridge Security Research” produced by Chengdu Chain Security, in the previous article (Cross-Chain Bridge Security Research (2) | What does the first decentralized robbery of the Nomad bridge bring us?), we conduct a professional technical analysis of the Nomad bridge protocol in detail.

Today, the Chengdu Chain Security Research Team will conduct a safe dialysis of Polygon, a polygon warrior, so keep reading.

Who is 1_Polygon?

Polygon is Ethereum’s layer2 scaling solution, with a vision to build Ethereum’s blockchain internet. Polygon provides a common framework that allows developers to leverage Ethereum’s security to create customized, application-focused chains and provide an interoperable network that combines a variety of different scaling schemes such as: zk-rollup, PoS, etc. Among them, Polygon PoS is currently the most mature and well-known scaling solution on Polygon. It uses the sidechain for transaction processing to achieve the purpose of improving transaction speed and saving gas consumption, and the network structure mainly includes the following three layers:

多边形战士Polygon安全透析:如何保证跨链的安全性和稳定性?

Ethereum Layer:

A series of contracts on the Ethereum mainnet, mainly including: Staking, Checkpoint, and Reward contracts, are responsible for the staking management functions related to PoS stakes, including: providing the staking function of the MATIC native token, so that anyone who stakes the token can join the system as a validator, verifying the conversion of the Polygon network to obtain staking rewards, punishing validators for double signatures, validator downtime and other illegal behaviors, and saving checkpoints.

Heimdall Layer:

The proof-of-stake validator layer, which consists of a set of PoS Heimdall nodes, is responsible for submitting the checkpoints of the Polygon network to the Ethereum mainnet while listening on a set of staking contracts deployed on Ethereum. The main process is as follows: first, select a subset of active validators in the validator pool as block producers, who will be responsible for creating blocks at the Bor layer and broadcasting them, then validate the Merkle root hash and append signatures based on the checkpoints submitted by Bor, and finally, the proposer will be responsible for collecting all validator signatures for the specified checkpoint, and if the number of signatures reaches more than 2/3, the checkpoint will be submitted on Ethereum.

Bor Layer:

The block producer layer, which consists of a group of block producers regularly selected by a committee of validators on the Heimdall layer, is a subset of validators responsible for aggregating transactions on the Polygon sidechain and generating blocks. This layer periodically publishes checkpoints to the Heimdall layer, where the checkpoint represents a snapshot of the Bor chain, as shown in the image below.

多边形战士Polygon安全透析:如何保证跨链的安全性和稳定性?

2_Polygon Interoperability

2.1 Checkpoint

The checkpoint mechanism is a mechanism to synchronize the data of the Bor layer to Ethereum, where the synchronized data is a checkpoint, that is, a snapshot of the block data of the Bor layer contained in a checkpoint interval, the source code is as follows:

多边形战士Polygon安全透析:如何保证跨链的安全性和稳定性?

Proposer: Proposer, which is also selected by validators, block producers and proposers are a subset of validators, and their responsibilities are determined by their stake in the overall pool

RootHash: is a Merkle hash generated from the Bor block between StartBlock and EndBlock

The following is a pseudo-code for the Bor block numbered 1 to n to generate the RootHash value:

多边形战士Polygon安全透析:如何保证跨链的安全性和稳定性?

In summary, this value is the root hash value of the Merkel tree, which is composed of the block number in the Bor block header, the block timestamp, the transaction tree root hash value tx hash, and the keccak256 hash value calculated from the receipt tree root hash.

AccountRootHash: A Merkle hash of the validator-related account information that needs to be sent to each checkpoint on Ethereum, and the hash value of the individual account information is calculated as follows:

多边形战士Polygon安全透析:如何保证跨链的安全性和稳定性?

The AccountRootHash is generated from the account Merkle tree root hash in the same way as the RootHash value.

2.2 StateSync

StateSync refers to the synchronization of Ethereum data to the Polygon Matic chain, which is mainly divided into the following steps:

  1. First, the contract on Ethereum will trigger the syncState() function in StateSender.sol for state synchronization

  2. The syncState() function will emit an event event as follows:

多边形战士Polygon安全透析:如何保证跨链的安全性和稳定性?

  1. All validators in the Heimdall layer will receive the event, and one of the validators will package the transaction into the heimdall block and add it to the pending state synchronization list;

  2. The node of the bor layer will obtain the above list to be synchronized through the API, and hand it over to the contract of the bor layer for further business logic processing.

2.3 Polygon Bridge

Polygon Bridge enables a two-way cross-chain channel between Polygon and Ethereum, making it easier for users to transfer tokens between two different chain platforms without third-party threats and market liquidity constraints. There are two types of Polygon Bridge, PoS and Plasma, and both have the following similarities in the transfer of assets between Polygon and Ethereum:

  1. First you need to map the token on Ethereum to Polygon, as shown in the image below:

多边形战士Polygon安全透析:如何保证跨链的安全性和稳定性?

  1. Two-way anchoring technology (Two-way Peg) is also used, i.e

a: All token assets transferred from Ethereum will be locked on Ethereum first, and the same number of mapped tokens will be minted on Polygon;

b: In order to withdraw token assets to Ethereum, you first need to burn these mapped tokens on Polygon, and then unlock the assets locked on Ethereum;

The following figure shows the comparison between PoS Bridge and Plasma Bridge:

多边形战士Polygon安全透析:如何保证跨链的安全性和稳定性?

As can be seen from the above figure, in terms of security, PoS Bridge relies on the security of the external validator set, while Plasma relies on the security of the Ethereum main chain. At the same time, when users make cross-chain asset transfers (such as transferring tokens from Polygon to Ethereum), PoS only requires a checkpoint interval, about 20 minutes to 3 hours, while Plasma requires a 7-day dispute challenge period. At the same time, PoS supports more standard tokens, while Plasma only supports three types, including: ETH, ERC20, ERC721.

3_Cross-chain messaging—PoS Bridge

The PoS Bridge mainly consists of two functions: Deposit refers to the transfer of users’ assets on Ethereum to Polygon, and Withdrawals refers to the withdrawal of assets from Polygon to Ethereum.

Deposit

The following is an example of user Alice using PoS Bridge to send token assets from her Ethereum account to her Polygon account:

  1. If the token assets you want to transfer are ERC20, ERC721, or ERC1155, you need to authorize the token you want to transfer through the approtect function. As shown below, the corresponding number of tokens is authorized to the erc20Prefer contract by calling the approtect method in the token contract on Ethereum.

多边形战士Polygon安全透析:如何保证跨链的安全性和稳定性?

The approtect function has two parameters:

  • Spender: The destination address where the user authorizes the spending of tokens

  • amount: The number of tokens that can be spent

  1. After the above authorization transaction is confirmed, the user then locks the token into the erc20Preliminary contract on Ethereum by calling the depositFor() method of the RootChainManager contract. Here, if the asset type to be transferred is ETH, depositEtherFor() is called. The details are as follows:

多边形战士Polygon安全透析:如何保证跨链的安全性和稳定性?

The depositFor function has three parameters:

  • user: The address of the user who received the deposit tokens on Polygon

  • rootToken: The token address on the main Ethereum chain

  • depositData: The number of tokens encoded by ABI

The following is the specific code of the depositFor function in the RootChainManager contract:

多边形战士Polygon安全透析:如何保证跨链的安全性和稳定性?

多边形战士Polygon安全透析:如何保证跨链的安全性和稳定性?

Analyzing the source code, it can be seen that the function first obtains the predicate contract address corresponding to the token, and then calls its lockTokens() function to lock the token in the contract. Finally, syncState() will be called for state synchronization by _stateSender, which can only be called by the state sender set by admin.

  1. The syncState() function in StateSender.sol will submit the event StateSynced, specifically:

多边形战士Polygon安全透析:如何保证跨链的安全性和稳定性?

The first parameter is the sequence number index of the log, the second parameter is used to verify whether the caller is a registered legitimate contract address, and the third parameter is the data that needs to be synchronized with the state. The transaction is added to the Heimdall block and is added to the pending state synchronization list.

多边形战士Polygon安全透析:如何保证跨链的安全性和稳定性?

  1. Then, after the bor node on the Polygon Matic chain obtains the StateSynced event in the state synchronization list through the API, the ChildChainManager contract on the chain will call the onStateReceive() function, which is used to receive the synchronization data uploaded from Ethereum, and proceed to the next step according to the business logic type of the state synchronization:

多边形战士Polygon安全透析:如何保证跨链的安全性和稳定性?

data: bytes32 syncType and bytes syncData. When syncType is mapping, syncData is the encoded rootToken address, childToken address, and bytes32 tokenType, and when syncType is deposit, syncData is the encoded user address. The rootToken address and depositData of the bytes type. depositData is the quantity in REC20 and tokenId in ERC721.

多边形战士Polygon安全透析:如何保证跨链的安全性和稳定性?

  1. Since this is a Deposit business, the _syncDeposit() function will be called. This function will first decode the syncData in the corresponding format to obtain the corresponding rootToken, user address, and depositData. Then check whether the rootToken has a corresponding mapping token on the polygon, and call the deposit() function of the childToken if there is.

多边形战士Polygon安全透析:如何保证跨链的安全性和稳定性?

  1. Here we take the ERC20 token contract as an example to introduce how to deposit the mapping token contract. This function transfers the corresponding number of tokens to the user’s account.

多边形战士Polygon安全透析:如何保证跨链的安全性和稳定性?

The function has two parameters:

  • user: The address of the user who is making a deposit

  • depositData: The amount encoded in ABI

Withdrawals

The following is an example of user Alice using PoS Bridge to withdraw the funds deposited in her Polygon account to her Ethereum account:

  1. When the user withdraws, it needs to burn the corresponding number of mapped tokens by calling the withdraw() function of the mapping token contract on the Polygon chain.

多边形战士Polygon安全透析:如何保证跨链的安全性和稳定性?

Withdraw contains only one parameter: the number of tokens that will be burned. The withdraw() function in the corresponding token contract is as follows:

多边形战士Polygon安全透析:如何保证跨链的安全性和稳定性?

  1. The above transactions will be included in the checkpoint after about 20 minutes to 3 hours, and the validator will submit it to Ethereum.

  2. Once the transaction is added to the checkpoint and submitted to Ethereum, the exit() function of the RootChainManager contract on Ethereum will be called, which will confirm the validity of the withdraw transaction on Polygon by verifying the submitted checkpoint content, and trigger the corresponding Predicate contract to unlock the user’s deposited tokens.

The Proof proof inputData passed into the function includes the following data:

  • headerNumber: Contains the checkpoint block header for the withdraw transaction

  • blockProof: Prove that the block header in the child chain is the leaf node of the merkle root that committed

  • blockNumber: The block number on the child chain that contains the withdraw transaction

  • blockTime: The block timestamp of the withdraw transaction

  • txRoot: The root value of the block transaction tree

  • receiptRoot: The root value of the block receipt tree

  • Receipt: A receipt for a withdrawal transaction

  • receiptProof: Merck proof of the withdrawal transaction receipt

branchMask: The path to the receipt represented by the 32 bits in the receipt tree

  • receiptLogIndex: The log index that is read from the receipt tree

The following is the core logic of the function, which mainly includes three parts: the first part is to verify the validity of the withdraw transaction receipt, the second part is to verify whether the checkpoint contains the transaction block, and the third part is to call the exitTokens() function in the predicate contract to send the locked tokens to the user.

多边形战士Polygon安全透析:如何保证跨链的安全性和稳定性?

  1. Take the ERC20Predicate contract as an example, that is, after decoding the receiver, sender, and the number of tokens sent from the log, a given number of tokens will be sent to the user.

多边形战士Polygon安全透析:如何保证跨链的安全性和稳定性?

According to the source code analysis of the PoS Bridge cross-chain messaging process, the function calls of the entire process can only be called by the role specified by the validator, so the security of the cross-chain is only guaranteed by the PoS (notary).

4_Cross-chain messaging—Plasma Bridge

Plasma Bridge also includes two functions: Deposit and Withdrawals, as shown in the following figure:

多边形战士Polygon安全透析:如何保证跨链的安全性和稳定性?

Polygon Plasma is slightly different from the Bitcoin Plasma MVP implementation introduced in the first article of our cross-chain bridge series, which mainly uses the account-based Plasma MoreVP model. Compared with Plasma, the algorithm has been partially improved in the withdraw part.

Since the token transfer of ERC20 and ERC721 is realized through an event log similar to a Bitcoin UTXO, let’s first introduce the event:

  • input1: The balance of the sender’s account before the transfer

  • input2: The recipient’s account balance before the transfer

  • output1: The balance of the sender’s account after the transfer

  • output2: The recipient’s account balance after the transfer

多边形战士Polygon安全透析:如何保证跨链的安全性和稳定性?

Secondly, because the original Plasma MVP is generated by a single operator or a small number of block producers, there are two attack scenarios on Polygon:

Operator Evil:

Previous article (Research on Cross-Chain Bridge Security (2) | Nomad cross-chain bridge) mentions that when a user’s transaction is packaged into a Plasma block by the Operator, there is an unavailability of off-chain data. Therefore, when a user makes an exit transaction, if they start withdrawing from an older transaction, the Operator can challenge it with one of their most recent transactions, and the challenge will be successful. At the same time, due to the PoS checkpointing mechanism used in Plasma, if the Operator colludes with validators to do evil, they can even forge some state transitions and submit them to Ethereum.

User Evil:

Users continue to spend tokens on Polygon after initiating an exit transaction, similar to a cross-chain double-spend.

In summary, Polygon’s Plasma MoreVp algorithm uses another algorithm for calculating exit priority, which is to exit from the most recent transaction. Because this method uses a LogTransfer event similar to UTXO, as long as the user’s legitimate transaction uses the correct input1 and input2, even if some malicious transactions are packaged before the user’s transaction, the user’s transaction can be handled correctly because the user’s transaction only comes from a valid input. The relevant pseudocode is as follows:

多边形战士Polygon安全透析:如何保证跨链的安全性和稳定性?

Deposit

Let’s take the example of user Alice using Plasma Bridge to send token assets from her Ethereum account to her Polygon account:

  1. First of all, users also need to authorize the token assets they need to transfer to the depositManager of the Polygon contract on the main chain (Ethereum) through the approtect function.

  2. After the authorized transaction is confirmed, the user calls the erc20token.deposit() function to trigger the depositERC20ForUser() function of the depositManager contract and deposit the user’s ERC20 token assets.

多边形战士Polygon安全透析:如何保证跨链的安全性和稳定性?

  1. When the Ethereum mainnet confirms the deposit transaction, it will create a block containing only this transaction, and send it to the childChain contract on the Polygon network using the state synchronization mechanism, mint the same amount of mapped coins and deposit them into the user’s account on Polygon.

多边形战士Polygon安全透析:如何保证跨链的安全性和稳定性?

Note: According to the analysis of the source code of the childChain contract, Plasma only supports three types, including: ETH, ERC20, and ERC721.

Withdraw

When a user wants to use the Plasma bridge to withdraw assets from Polygon to Ethereum, they will go through the following steps:

  1. The user burns the mapped token assets on the Polygon chain by calling the withdraw() function of the mapped coin on Polygon:

多边形战士Polygon安全透析:如何保证跨链的安全性和稳定性?

You can also call the withdrawStart() interface implementation of the Plasma Client on Polygon.

  1. The user can call the startExitWithBurntTokens() function in the ERC20Predicate contract, which will first call WithdrawManager.verifyInclusion() to verify whether the checkpoint contains the withdraw transaction and the corresponding receipt, the code is as follows:

多边形战士Polygon安全透析:如何保证跨链的安全性和稳定性?

After the verification is passed, WithdrawManager.addExitToQueue() will be called to insert it into the message queue in order of priority:

多边形战士Polygon安全透析:如何保证跨链的安全性和稳定性?

Finally, addExitToQueue() calls _addExitToQueue() to mint an NFT as a refund voucher:

多边形战士Polygon安全透析:如何保证跨链的安全性和稳定性?

  1. The user waits for a 7-day challenge period

  2. After the challenge period is completed, you can call the WithdrawManager.processExits() function to send tokens to the user.

The function is divided into two steps: first, confirm whether the withdraw transaction in the message queue has passed the 7-day challenge period, and if the challenge period has passed, remove the transaction from the queue:

多边形战士Polygon安全透析:如何保证跨链的安全性和稳定性?

Then, determine whether the refund voucher NFT has been deleted during the challenge period, and if it is not, the NFT will be destroyed and the corresponding assets will be returned to the user:

多边形战士Polygon安全透析:如何保证跨链的安全性和稳定性?

5_Polygon Plasma Bridge double-spend vulnerability

On October 5, 2021, White Hat Gerhard Wagner filed a Polygon vulnerability that could lead to a double-spend attack involving $850 million, for which the White Hat received an official $2,000,000 bug bounty from Polygon.

In the introduction of Plasma Bridge above, we know that the complete Withdraw transaction process is as follows:

- The user initiates a Withdraw transaction on Polygon, which burns the user’s tokens on Polygon;

- After a checkpoint interval (approximately 30 minutes), wait for the withdraw transaction to be included in the checkpoint;

- More than 2/3 of the validators sign and submit it to Ethereum, at which point the user calls startExitWithBurntTokens() in the ERC20PredicateBurnOnly contract to verify whether the checkpoint contains burn transactions;

- If the verification is passed, an NFT refund voucher will be minted and sent to the user

- Users wait for a 7-day challenge period

- Call WithdrawManager.processExits() to destroy the NFT and refund the user

Note: In order to prevent transaction replay (double-spend attacks), Polygon uses NFTs as proof of refund to uniquely identify a Withdraw transaction. However, due to the defect of NFT ID generation, attackers can construct parameters to generate multiple NFTs with different IDs using the same valid Withdraw transaction, and then use these NFTs for refund transactions, thus achieving a “double-spend attack”.

Here’s a closer look at how to generate NFTs:

  1. From the source code analysis above, it can be seen that addExitToQueue() will call _addExitToQueue() to mint an NFT:

多边形战士Polygon安全透析:如何保证跨链的安全性和稳定性?

According to the parameter analysis, exitid = priority, then the ID of the NFT is generated by the left shift of the age priority in Plasma Bridge.

多边形战士Polygon安全透析:如何保证跨链的安全性和稳定性?

  1. As can be seen from the above source code analysis, age is the return value of the WithdrawManager.verifyInclusion() function, which will first verify the validity of the withdraw transaction, and then generate the corresponding age if the verification is passed. In the validation logic, the value decoded by the controllable parameter data is branchMaskBytes:

多边形战士Polygon安全透析:如何保证跨链的安全性和稳定性?

This value is also used when generating age:

多边形战士Polygon安全透析:如何保证跨链的安全性和稳定性?

  1. Trace the MerklePatriciaProof.verify() function called in the transaction verification logic, and find that the function calls _getNibbleArray() to transcode branchMaskBytes:

多边形战士Polygon安全透析:如何保证跨链的安全性和稳定性?

  1. Continue to track the decoding function, which discards part of the value when transcoding branchMaskBytes, and this way of losing the value will cause different values to be transcoded to obtain the same decoded value. Specifically, if the first hexadecimal bit (half a byte) of the incoming hp-encoded value b is 1 or 3, the second hexadecimal bit is parsed. Otherwise, the first byte is simply ignored.

多边形战士Polygon安全透析:如何保证跨链的安全性和稳定性?

If an attacker constructs a branchMaskBytes parameter so that the first hexadecimal bit is not equal to 1 and 3, there are 14*16 = 224 ways to obtain the same transcoded value.

The specific attack process is as follows:

- Deposit a large amount of ETH/tokens to Polygon via Polygon Plasma

- Initiate a Withdraw trade on Polygon and wait for a 7-day challenge period

- Modify the first byte of the branchMaskBytes parameter in the withdraw transaction (the same valid transaction can be resubmitted up to 223 times) and initiate the withdraw transaction repeatedly

In summary, this vulnerability is mainly due to a problem in the ID algorithm design of the NFT that generates the refund voucher to prevent replay, resulting in the same refund transaction can generate different NFTs, resulting in a double-spend attack. It turns out that the first byte of the encoded branch mask should always be 0x00. The fix is to check if the first byte of the encoded branch mask is 0x00 and don’t treat it as an incorrect mask.

多边形战士Polygon安全透析:如何保证跨链的安全性和稳定性?

Well, today’s sharing is over, in the next issue, the Chengdu Chain Security Security Research Team will introduce the security research of another cross-chain project, please look forward to it.

View Original
This page may contain third-party content, which is provided for information purposes only (not representations/warranties) and should not be considered as an endorsement of its views by Gate, nor as financial or professional advice. See Disclaimer for details.
  • Reward
  • Comment
  • Repost
  • Share
Comment
0/400
No comments
  • Pin

Trade Crypto Anywhere Anytime
qrCode
Scan to download Gate App
Community
  • 简体中文
  • English
  • Tiếng Việt
  • 繁體中文
  • Español
  • Русский
  • Français (Afrique)
  • Português (Portugal)
  • Bahasa Indonesia
  • 日本語
  • بالعربية
  • Українська
  • Português (Brasil)