# How to implement SPV of Arbitrum with Solidity
###### tags: `Public`
Author: celestialRiver
## 1. Antecedent Knowledge:
* Arbitrum is divided into *[Classic stack and Nitro stack](https://developer.offchainlabs.com/why-nitro#nitro-vs-classic)*, which can also be called system, hereinafter referred to as’ Classic’ and ‘Nitro’ respectively.
* Since August 31, 2022, no public arbitrum chain will continue to use the Arbitrum *[Classic](https://github.com/OffchainLabs/arbitrum)* stack, but the Arbitrum *[Nitro](https://github.com/OffchainLabs/nitro)* stack will be used instead
* *[Definition of Patricia Merkle Tree(MPT)](https://ethereum.org/en/developers/docs/data-structures-and-encoding/patricia-merkle-trie/)*
## 2. Life Cycle
### 2.1 Arbitrum Sequencer

Source: [*offchainlabs*](https://developer.offchainlabs.com/assets/images/seq-then-exec-0ec8fef09d47390cee2d6c2f7b5634d3.png)
### 2.2 TxHash
```
TxHash
=>
InboxMessageg
=>
SequencerMessage
=>
SequencerBatchItem
=>
SequencerFeedItem
=>
BroadcastFeedMessage
=>
BroadcastMessage
=>
SubmissionInputData
```
* In *SequencerBatchItem => SequencerFeedItem* part, Classic and Nitro both use **base64** format to process data.
* In *SequencerFeedItem => BroadcastFeedMessage* part, Nitro uses a general data compression algorithm called **brotli** to compress batches
---
## 3. Concrete Implementation
### 3.1 Procedures
```solidity
1.TxHash => InputData => SubmissionInputData
//Prove that the InputData generated based on the TxHash submitted by the user is included in the SubmissionInputData provided by the local client
2.SubmissionInputData => SubmissionTxHashRLP => NewSubmissionTxHash
//Prove that the NewSubmissionTxHash generated by SubmissionTxHashRLP based on SubmissionInputData is consistent with the SubmissionTxHash submitted by the user
3.NewSubmissionTxHash + "MPT_proof" => L1Blockhash => Next
//Prove that the generated NewSubmissionTxHash is proved by MPT in combination with the block data and the L1Blockhash is passed to the next process
4.L1Blockhash + "OtherProof" => NowL1BlockHash
//Prove that L1Blockhash is a past block of NowL1BlockHash
```
### 3.2 Details
The user needs to provide TxHash and SubmissionTxHash.
***3.2.1 TxHash => InputData => SubmissionInputData***
***Purpose***: *To verify the validity of an L2 transaction in L1 transaction parameters.*
***Analysis***: *In view of the importance of this step, we need to analyze this step in detail.*
---
> **Classic**
The following proof methods are only applicable to ***Classic***, but they are also prerequisites for understanding ***Nitro***
* Suggest a transaction on Arbitrum: ***[0x6a1d](https://arbiscan.io/tx/0x6a1d569ca0a8a64d868092ed42ad7d194293c3059cf9780991174f43dd8d3ba5)***, record the hash as ***TxHash0x6a1d***
* Batches containing ***TxHash0x6a1d*** are packaged, compressed, and published to L1 by the Sequencer, and that hash is recorded as ***[SubmissionTxHash0xb150](https://etherscan.io/tx/0xb15092818da5384eb1e846dd1b1f4b92065cd1b71482867c456960a8c67367b2)***
* The ***InputData*** can be obtained through ***SubmissionTxHash0xb150***, which is recorded as ***SubmissionInputData0xb150***.
* The corresponding ***Transaction Info*** can be obtained through ***TxHash0x6a1d***, which is recorded as ***TxInfo0x6a1d***
```solidity
// TxInfo0x6a1d
{
hash: '0x6a1d569ca0a8a64d868092ed42ad7d194293c3059cf9780991174f43dd8d3ba5',
type: 0,
accessList: null,
blockHash: '0x4dd97d8cbb5ef3e4fdddbc99ae813e8713f478902038dee8d1c672117ed35cfd',
blockNumber: 17237171,
transactionIndex: 0,
confirmations: 22649108,
from: '0xC0aF3690cCEdcc353AEb33cC0bB3eF63EF1070ba',
gasPrice: BigNumber { _hex: '0x1c16ab5a', _isBigNumber: true },
gasLimit: BigNumber { _hex: '0x079366', _isBigNumber: true },
to: '0xC266551ef06b976AbC403F998Bd3Ae2398403937',
value: BigNumber { _hex: '0x07911ad5a14714', _isBigNumber: true },
nonce: 2,
data: '0x',
r: '0x8698fd9762461c6b042007e99a236d50f6cdaf5b6498e56832cbdfb470de6669',
s: '0x6058d9801636a3f6c90db74b55c8dc611030715989a3b896e1fa66b30d8f2c9e',
v: 84358,
creates: null,
chainId: 42161,
wait: [Function (anonymous)]
}
```
* ***[L2Message data structure](https://github.com/OffchainLabs/arbitrum/blob/f565e0571b6883bd7021f926ba823e9d0b609b5f/packages/arb-evm/message/l2Message.go#L534)*** and ***Tx Info processing*** in Arbitrum:
```solidity
txData := []interface{}{
t.SequenceNum, //Nonce
t.GasPrice, //GasPrice
t.GasLimit, //GasLimit
dest, //To
t.Payment, //Value
t.Calldata, //Data
v, //V
t.R, //R
t.S, //S
}
```
* [Handling **Nonce, GasPrice, GasLimit, To, Value, Data** from Tx Info:](https://github.com/OffchainLabs/arbitrum/blob/f565e0571b6883bd7021f926ba823e9d0b609b5f/packages/arb-evm/message/data.go#L245)
```solidity
func encodeUnsignedTx(tx CompressedTx) ([]byte, error) {
nonceData, err := rlp.EncodeToBytes(tx.SequenceNum)
if err != nil {
return nil, err
}
gasPriceData, err := rlp.EncodeToBytes(tx.GasPrice)
if err != nil {
return nil, err
}
gasLimitData, err := rlp.EncodeToBytes(tx.GasLimit)
if err != nil {
return nil, err
}
paymentData, err := encodeAmount(tx.Payment)
if err != nil {
return nil, err
}
var data []byte
data = append(data, nonceData...)
data = append(data, gasPriceData...)
data = append(data, gasLimitData...)
if tx.To == nil {
data = append(data, 0x80)
} else {
destData, err := tx.To.Encode()
if err != nil {
return nil, err
}
data = append(data, destData...)
}
data = append(data, paymentData...)
data = append(data, tx.Calldata...)
return data, nil
}
```
* [Handling ***V,R,S*** from Tx Info:](https://github.com/OffchainLabs/arbitrum/blob/f565e0571b6883bd7021f926ba823e9d0b609b5f/packages/arb-evm/message/data.go#L245)
```solidity
func encodeECDSASig(v byte, r, s *big.Int) []byte {
data := make([]byte, 0, 65)
data = append(data, ethmath.U256Bytes(new(big.Int).Set(r))...)
data = append(data, ethmath.U256Bytes(new(big.Int).Set(s))...)
data = append(data, v)
return data
}
```
* By applying RLP(Recursive Length Prefix) to partial data of ***TxInfo0x6a1d*** and processing ***V, R, S***, we shall obtain the following data, noted as ***TxInfoRLP0x6a1d***
```
// TxInfoRLP0x6a1d
02841c16ab5a8307936694c266551ef06b976abc403f998bd3ae23984039378707911ad5a14714008698fd9762461c6b042007e99a236d50f6cdaf5b6498e56832cbdfb470de66696058d9801636a3f6c90db74b55c8dc611030715989a3b896e1fa66b30d8f2c9e
```
* Corresponding:
```solidity
02 // SequenceNum/Nonce
84 // 0x84 - 0x80 = 4 bytes
1c16ab5a // GasPrice
83 // 0x83 - 0x80 = 3 bytes
079366 // GasLimit
94 // 0x94 - 0x80 = 20 bytes
c266551ef06b976abc403f998bd3ae2398403937 // To
87 // 0x87 - 0x80 = 7 bytes
07911ad5a14714 // Value
00 // V
8698fd9762461c6b042007e99a236d50f6cdaf5b6498e56832cbdfb470de6669 // R
6058d9801636a3f6c90db74b55c8dc611030715989a3b896e1fa66b30d8f2c9e // S
```
* Find it in ***SubmissionInputData0xb150***:

* Hence, In ***Classic***, you can use ***TxHash*** to prove whether an L2 transaction exists in L1, that is, you can verify the validity of an L2 transaction in L1 transaction parameters.
---
> **Nitro**
* The life cycle of *Nitro* is basically the same as that of *Classic*, but in the process of ***SequencerFeedItem => BroadcastFeedMessage***, the ***brotli*** algorithm is used to compress the entire ***batch***. Because of this, the proof method applicable to Classic is not applicable to *Nitro*.
* Certification scheme applicable to Nitro (Scheme 3 is valid after testing):
1. Get ***SubmissionInputData0xb150***, deconstruct its data, get ***Batch*** data in it, and finally judge whether it contains ***TxInfoRLP0x6a1d***. (Failed)
2. Get all other ***txs*** in ***the same Batch block*** of ***Tx0x6a1d***, base64 all of them, and then assemble them into Batch data. Use ***brotli*** to compress them. Finally, compare the compressed data with ***SubmissionInputData0xb150***. (Failed)
3. Running an ***[Arbitrum Nitro Node](https://developer.offchainlabs.com/node-running/running-a-node)*** can directly synchronize the L1 status of each L2 transaction without proving through ‘Sequencer’ and other steps.
Arguments of Nitro:
* ***TxHash***: Transaction from Arbitrum. (User provided)
* ***SubmissionTxHash***: SubmissionTx on Ethereum L1. (User provided)
* ***SubmissionInputData***: InputData from SubmissionTxHash. (Local client)
* Run the ***[Arbitrum Nitro Node](https://developer.offchainlabs.com/node-running/running-a-node)*** off-chain, supply the ***TxHash*** provided by the user, and prove whether the ***TxHash*** is included in ***SubmissionInputData*** through the node data. If it is proven, go to the next step.
---
> Supplementary note — Assumption on verifying the effectiveness of TxHash:
> * We plan to add a new step to verify the effectiveness of **TxHash**. This step will be after Step 1 and before Step 2
> * Since the transaction **status** of TxHash on L1 exists in the **Arbitrum Nitro Node**, it can be assumed that it is based on the data provided by the Arbitrum Nitro Node.
> * Dropped the failed transactions, and build a Merkel tree off-chain by using the successful transaction hash. At the same time, build a Merkel tree on-chain. After the user provides **TxHash** and **proves** through the first step, verify the **MPT data** provided by the **TxHash** on-chain client. First, verify whether the **two Merkel trees** is consistent, and then verify whether **TxHash** exists in the Merkel tree.
>
> Question:
> 1. In what form is the transaction hash packed and compressed?
> 2. How often is it packed and compressed?
>
> Reference:
> [cirom and snarkjs](https://learnblockchain.cn/article/1078)
---
***3.1.2 SubmissionInputData => SubmissionTxHashRLP => NewSubmissionTxHash***
***Purpose**: To verify the validity of the submitted L1 transaction **parameter** that exists in the L1 transaction.*
***Arguments:***
* ***SubmissionTxHash***: from the last procedure
* ***SubmissionInputData***: from the last procedure
* ***SubmissionTxHashRLP***: RLP encode of SubmissionInputData
***Procedures:***
Upload ***SubmissionTxHash*** and ***SubmissionTxHashRLP*** on-chain for verification. Because ***SubmissionTxHashRLP*** contains ***SubmissionInputData***, ***Keccak256*** is used in the contract to calculate the hash of ***SubmissionTxHashRLP***, which is recorded as ***NewSubmissionTxHash***, and to verify whether ***NewSubmissionTxHash*** is consistent with ***SubmissionTxHash***. If it is consistent, the verification is passed.
```solidity
//Solidity
function validateRLPTxInfoBySubTxHash(
bytes memory rlpTxInfo,
bytes[] memory header
) internal view;
```
---
***3.1.3 NewSubmissionTxHash + MPTproof => L1Blockhash => Next***
***Purpose**: To verify the validity of the submitted **L1 transaction** that exists in the **L1 Block**.*
***Arguments:***
* ***SubmissionTxHashRLP***: from the last procedure
* ***NewSubmissionTxHash***: from the last procedure
* ***L1BlockNumberMPTProof***: ***MPT*** data from SubmissionTx’s L1 Block
* ***L1BlockTxRootHash***: ***Root Hash*** from SubmissionTx’s L1 Block
* ***L1BlockHash***: ***Block Hash*** from SubmissionTx’s L1 Block
Upload five arguments on-chain for verification. The verification here is divided into four parts. If all four parts are passed, the verification is passed.
1. Verify the connection between ***SubmissionTxHashRLP*** and ***L1BlockNumberMPTProof***
```solidity
// Solidity
function validateRLPTxInfoByProof(
bytes memory rlpTxInfo,
bytes[] memory proof
) internal view;
```
2. Verify ***L1BlockTxRootHash***
```solidity
// Solidity
function validateTxRootHash(bytes[] memory proof, bytes[] memory header)
internal
view
returns (bytes32 rootHash);
```
3. Verify ***L1BlockHash***
```solidity
// Solidity
function validateBlockHash(bytes[] memory header) internal view;
```
4. Verify ***L1BlockNumberMPTProof***
```solidity
// Solidity
function validateMPTProof(
bytes32 rootHash,
bytes memory mptKey,
bytes[] memory proof
) internal view returns (bytes memory value);
```
---
***3.1.4 L1Blockhash + OtherProof => NowL1BlockHash***
***Purpose**: To verify the validity of the **L1 Block** that exists in the **L1 Blockchain**.*
***Arguments:***
* ***L1Blockhash***: from the last procedure
* ***L1BlockNumber***: corresponding block number
*
Here we are divided into two parts:
> **Off-chain:**
1. Get the block information from ***L1BlockNumber*** to ***the latest block number***, and sort them in order, 1.. 2.. 3… n.
2. Process the Raw Data of the second block to get the ***RLP*** code, and then use ***Keccak256*** to hash it to get the ***BlockHash*** of the second block.
3. Compare this ***off-chain BlockHash*** with the ***on-chain BlockHash***. If the same, the verification is passed.
4. Get ***ParentHash*** from the second block, and compare this to ***the first BlockHash***. If the same, the verification is passed.
5. Prove to the ***n-th*** block in turn.
6. It is required that the calculation time of the off-chain part is less than ***1h***, and if it exceeds ***1h***, it will continue to prove from the n-th block to the latest block number.
7. Arguments:
* ***ChainOffL1BlockHash***: BlockHash of the n-th block.
* ***ChainOffL1BlockNumber***: BlockNumber of the n-th block.
> ***On-chain:***
* Use ***blockhash (uint blockNumber)*** function in the contract to obtain the block hash with the block number of ***ChainOffL1BlockNumber***, which is recorded as ***NowL1BlockHash***.
* Compare ***NowL1BlockHash*** is equal to ***ChainOffL1BlockHash***. If the same, the verification is passed.
```solidity
// Solidity
function validateBlockNumberhash(
uint256 blockNumberPast,
bytes32 blockHashPast
) internal view;
```
---
**3.1.5** The L2 transaction hash submitted by the user can be considered **valid** after the above **4** steps are proven.