How to determine the zkSync’s SPV design?

Orbiter_Finance
5 min readDec 28, 2021

Event

There is a transaction whose txHash is 0x992ab3dd81fb2c98bfcce69b439ed6f061dce20304003433ede499ac0d5ac1ce, exsists NO. 47434 block in zkSync, which records TXinfo_0x992A.

We need to prove it happens in ZkSync on the L1 chain.

It is known that 47434 is committed in L1 to commit tx(0x8da41494671c582825805f6ea7bfe6c05bc4d623573fac3254eef865db0a5668).

The block number is 13852824 in L1 verify tx(0x6eca2e988159690205870d3a5c686494f88317779dbab0d8cd508744fe2d5e75).

The zkSync chain generates new roothash:286b3e6f8e1a168b41d16c859da9a1ba5c69d107bc601abf8b895b47050e90f6.

Tx life

The trusted RootHash available in the contract looks like this, using this method to get StoredBlockHashes(47434) and verify 47434 has been proven.

https://etherscan.io/address/0xabea9132b05a70803a4e85094fd0e1800777fbef#readProxyContract

StoredBlockHashes(47434)

47434 <= totalBlocksProven

We can see the value here : StoredBlockHashes(47434) = 0xc5c0fad4bfc9cc5c948c0216312f0765df4399aed4cbf60c21449a4dbc0b5336

The next step is to restore this process:

TX"0x992ab3dd81fb2c98bfcce69b439ed6f061dce20304003433ede499ac0d5ac1ce" => Encode => 
storedBlockHashes "
0xc5c0fad4bfc9cc5c948c0216312f0765df4399aed4cbf60c21449a4dbc0b5336"

It means to describe TXinfo_0x992A's life cycle in zkSync, and the data structure of the nodes in this cycle.

We now know three explicit data structures: TXinfo_0x992A, txhash_0x992A, and storedBlockHash_47434.

According to the zkSync’s commit tx in L1, we can also get a fourth explicit data structure: inputData_0x8da4149.

The order of data generation is as follows:

TXinfo_0x992A => inputData_0x8da4149 => storedBlockHash_47434

We don’t need txhash_0x992A.

Investigation: inputData_0x8da4149 => storedBlockHash_47434

function commitBlocks(StoredBlockInfo memory_lastCommittedBlockData, CommitBlockInfo[] memory _newBlocksData)

InputData_0x8da4149 is actually function_encode + stroreBlockInfo_pre47434 + CommitBlockInfo[].

Because commit sends three data at once, InputData_0x8da4149 needs to capture the commintBlockInfo_47434 associated with TXinfo_0x992A using the above structure.

Check the Github source code. There are two methods to convert commintBlockInfo_47434 to storedBlockHashes[47434] in CommitBlocks().

# 1. convert commintBlockInfo_47434 to StoredBlockInfo_47434 _lastCommittedBlockData = commitOneBlock(_lastCommittedBlockData, _newBlocksData[i]);# 2. convert StoredBlockInfo_47434 to storedBlockHashes[47434] storedBlockHashes[_lastCommittedBlockData.blockNumber] = hashStoredBlockInfo(_lastCommittedBlockData);

Look closer into how convert commintBlockInfo_47434 to StoredBlockInfo_47434.

Commitment includes publicData

Look closer into how convert StoredBlockInfo_47434to storedBlockHashes[47434].

function hashStoredBlockInfo(StoredBlockInfo memory_storedBlockInfo) internal 
pure returns (bytes32) {
return keccak256(abi.encode(_storedBlockInfo));
}

Investigation:TXinfo_0x992A => inputData_0x8da4149

According to the above investigation, we know that TXinfo took the following path:: txinfo => pubdata => commintBlockInfo_47434 => commintBlockInfos[] => commit tx input

Key investigation: txinfo => pubdata

There is a definition of “rollup” in the zkSync system and a definition of data structures in the ProtocalDoc.

  1. Transfer

2. Transfer to new

Rollup at TXinfo_0x992A .

opcode = 0x05

In this case, from and to in TXinfo_0x992A are both new accounts, and the op type belongs to transfer.

from_acount = 790468 => 000c0fc4

Follow this link to get the ID.

https://api.zksync.io/api/v0.2/accounts/0x2347b1b2b8cd9ee89ce4fa8c12f05bf65eb123e7

token = 0 => 00000000

https://api.zksync.io/api/v0.2/tokens/ETH

to_account = 719541 => 000afab5

https://api.zksync.io/api/v0.2/accounts/0xbd5f7f276d94afcac1505f93a98c0ec39a6e6a23

Packed_amount = 0.002376 => ?

Packed_fee = 0.0000668 => ?

Final result:0x05000c0fc400000000000afab5xxxxxxxxxxxxxx

It was found in inputData of [commit tx].

Note: The forgery of Pubdata should be considered in the scheme design.

How does Txinfo.from(0x2347b1b2b8cd9ee89ce4fa8c12f05bf65eb123e7)correspond on-chain to from_acount (000C0FC4) and to_acount.

storedBlockHash_47434.stateHash is composed of sparse Merkle trees, and the raw data is:

https://api.zksync.io/api/v0.2/blocks/47434

newStateRoot = 286b3e6f8e1a168b41d16c859da9a1ba5c69d107bc601abf8b895b47050e90f6

proof:newstateRoot + “otherdata” == storedBlockHash_47434

Suppose we have rebuilt the blockdata of the blockdata structure under StateRoot from the input data of the mainnet through zkSync’s blockchain client and written a method which is acount_info = getBlockdata[key]. In that case, we can get the following proof path:

1. acount_info_000c0fc4.address == 0x2347b1b2b8cd9ee89ce4fa8c12f05bf65eb123e7

2. proof(Hash(rpc(acount_info_000c0fc4)) + “MPT_Proof”) = newStateRoot

Rebuild the blockdata of the blockdata structure under StateRoot from the input data of the mainnet through zkSync’s blockchain client.

https://github.com/matter-labs/zksync/blob/master/docs/architecture.md

https://github.com/matter-labs/zksync/tree/master/core/bin/data_restore/src

Run it and rebuild the database from the database.

Proof scheme

the first proof route of proof

storedBlockHashs[47434] is used as the comparison hash.

Pros: Low gas fee and 1 million gas limit.

Cons: It isn’t universal enough to be an SPV solution for all rollups.

  1. TXinfo_0x992A => x => pubdata_47434
  2. pubdata + “otherDataHash” => commitment ‘’Note: Reuse createBlockCommitment() method, only need to rewrite it in part.”d
  3. commitment + “otherDataHash” => toredBlockHash_47434

the second proof route of proof

L1’s blockhash_aboutZksync47434Commit is used as the comparison hash.

Pros: It is universal enough to be an SPV solution for all rollups.

Cons: High gas fee and 2 million gas limit.” Note: Starkware’s Cairo code can overwrite it to put calculations off-chain.”

  1. TXinfo_0x992A => x => inputdata_L1TX_0x992ab3dd
  2. inputdata_L1TX_0x992ab3dd + “otherData” => L1TX_0x992ab3dd
  3. L1TX_0x992ab3dd + MPT_proof => L1blockhash_13852824
  4. L1blockhash_13852824 + “otherProof” => L1blockhash_nowBlockNumber

The below step is required because the blockhash(uint blockNumber) method only fetches Roothash from the current block to the 256th block in about an hour.

block.blockhash(uint blockNumber) returns (bytes32): hash of the given block - only works for 256 most recent, excluding current, blocks - deprecated in version 0.4.22 and replaced by blockhash(uint blockNumber).

All left is how to code, and Orbiter is on its way.

--

--

Orbiter_Finance

Orbiter Finance is a decentralized cross-rollup Layer 2 bridge with a contract only on the destination side.