# Zksync合约解析 用户可以layer2或者layer1(priority operation)上创建交易. 处理完用户交易请求后,operator会打包区块,并创建一个rollup操作。operator会提交block commitment到合约。zksync合约验证proof是否为true,为true则可以确定新状态。 ![](https://i.imgur.com/KjedqTF.png) ## 1 发起交易 以depositETH为例子, 会将用户请求encode成pubdata, ``` abi.encodePacked( uint8(OpType.Deposit), bytes4(0), // accountId (ignored) (update when ACCOUNT_ID_BYTES is changed) op.tokenId, // tokenId op.amount, // amount op.owner // owner ``` 然后放入priorityRequests队列中(用来存储链上交易的队列),可以看一下 PriorityOperation 的数据结构(表示layer1链上的交易,优先级较高)。 ``` /// @notice Priority Operation container /// @member hashedPubData Hashed priority operation public data /// @member expirationBlock Expiration block number (ETH block) for this request (must be satisfied before) /// @member opType Priority operation type struct PriorityOperation { bytes20 hashedPubData; //交易数据的hash uint64 expirationBlock; //超时时间 Operations.OpType opType; //操作类型 } ``` 这个队列存储用户这笔交易pubdata的hash,超时时间,和操作类型; ``` priorityRequests[nextPriorityRequestId] = PriorityOperation({ hashedPubData: hashedPubData, expirationBlock: expirationBlock, opType: _opType }); ``` 并发出事件,后续Server服务监听处理。 ``` emit NewPriorityRequest(msg.sender, nextPriorityRequestId, _opType, _pubData, uint256(expirationBlock)); ``` ## 2 提交区块 commitblock operator提交区块函数如下: ``` function commitBlocks(StoredBlockInfo memory _lastCommittedBlockData, CommitBlockInfo[] memory _newBlocksData) external nonReentrant ``` - _lastCommittedBlockData: 上一次已经committed的block,committed的区块在链上为StoredBlockInfo形式存储. - _newBlocksData: 新commit的block. 先看数据结构 ``` /// @notice Data needed to process onchain operation from block public data. struct OnchainOperationData { bytes ethWitness; // 只在ChangePubKey类型交易的时候用到 uint32 publicDataOffset; // 每笔交易在总publicdata里的偏移 } ``` 以及提交区块的数据结构 ``` /// @notice Data needed to commit new block struct CommitBlockInfo { bytes32 newStateHash; //新的状态hash,只在createBlockCommitment时候用一下,后端传入。 bytes publicData; //打包的交易数据 uint256 timestamp; OnchainOperationData[] onchainOperations; //待处理交易,里面包含每笔交易的偏移,以及ethwitness uint32 blockNumber; //区块高度 uint32 feeAccount; } ``` commitBlock函数最终返回StoredBlockInfo, 保存在链上。它的作用主要是对交易数据生成hash。 ``` struct StoredBlockInfo { uint32 blockNumber; // rollup block number uint64 priorityOperations; // 本区块priority operations 的数量 bytes32 pendingOnchainOperationsHash; // 所有交易的hash uint256 timestamp; bytes32 stateHash; //root hash of the rollup account tree state bytes32 commitment; //rollup 区块承诺 } ``` 然后依次执行每个commitBlockInfo,主要调用commitOneBlock函数 - 1 检查时间戳 - 2 执行collectOnchainOps(_newBlock)函数。 计算出 所有操作数据pubdata的hash, 优先交易的数量,以及onchainOperation的偏移。 ``` /// @dev processableOperationsHash - hash of the all operations that needs to be executed (Deposit, Exits, ChangPubKey) /// @dev priorityOperationsProcessed - number of priority operations processed in this block (Deposits, FullExits) /// @dev offsetsbment - array where 1 is stored in chunk where onchainOperation begins and other are 0 (used in commitments) ( bytes32 pendingOnchainOpsHash, uint64 priorityReqCommitted, bytes memory onchainOpsOffsetCommitment ) = collectOnchainOps(_newBlock); ``` - 3 对区块创建区块的commitment。 区块的commit:包含块高,feeAccount, 上一个块stateHash,当前块状态hash,时间戳,以及当前块的数据交易数据的整体hash. ``` function createBlockCommitment( StoredBlockInfo memory _previousBlock,CommitBlockInfo memory _newBlockData, bytes memory_offsetCommitment ) internal view returns (bytes32 commitment) { bytes32 hash = sha256(abi.encodePacked(uint256(_newBlockData.blockNumber), uint256(_newBlockData.feeAccount))); hash = sha256(abi.encodePacked(hash, _previousBlock.stateHash)); hash = sha256(abi.encodePacked(hash, _newBlockData.newStateHash)); hash = sha256(abi.encodePacked(hash, uint256(_newBlockData.timestamp))); bytes memory pubdata = abi.encodePacked(_newBlockData.publicData, _offsetCommitment); commitment = sha256(abi.encodePacked(hash, publicData)) // blockcommit ``` 最后返回 ``` StoredBlockInfo( _newBlock.blockNumber, priorityReqCommitted, pendingOnchainOpsHash, _newBlock.timestamp, _newBlock.newStateHash, commitment ); ``` 可见stateHash是由传入的newBlock里面的newStateHash直接赋值的。 (需要进一步看后端的newStateHash的生成逻辑) ## 3 proveBlock 验证零知识证明 ``` function proveBlocks(StoredBlockInfo[] memory _committedBlocks, ProofInput memory _proof) external nonReentrant ``` 主要逻辑是验证proof,并更改totalBlocksProven(当前已经证明的block)状态,传入已经commitBlock数组。 ``` bool success = verifier.verifyAggregatedBlockProof( _proof.recursiveInput, _proof.proof, _proof.vkIndexes, //此参数用于获取对应VK _proof.commitments, _proof.subproofsLimbs ); ``` ## 4 executeOneBlock, 执行block交易,处理资金充提; 执行区块,完成高优先级交易,以及处理资金提取 函数如下: ``` executeBlocks(ExecuteBlockInfo[] memory _blocksData, bool _completeWithdrawals) ``` 可以查看 ``` struct ExecuteBlockInfo { StoredBlockInfo storedBlock; bytes[] pendingOnchainOpsPubdata; } ``` 根据pendingOnchainOpsPubdata执行交易。 然后更改各存储状态,主要是更改最新请求的ID和已执行区块数量。 ``` firstPriorityRequestId += priorityRequestsExecuted; totalCommittedPriorityRequests -= priorityRequestsExecuted; totalOpenPriorityRequests -= priorityRequestsExecuted; totalBlocksExecuted += nBlocks; ``` ## 证明部分 ### verify合约 ``` function verifyAggregatedBlockProof( uint256[] memory _recursiveInput, uint256[] calldata _proof, uint8[] memory _vkIndexes, uint256[] memory _individualVksInputs, uint256[16] memory _subproofsLimbs ) external view returns (bool) ``` - _recursiveInput - input of the recursive aggregated circuit - _vkIndexes - index of the verification keys in the aggregated verification keys tree - _individualVksInputs - block commitments to be verified - _subproofsLimbs - data needed for recursive proof verification ### 获取VK #### 四种VK获取 1 KeysWithPlonkVerifier合约, 获取VK有 1 function getVkAggregated(uint32 _proofs) internal pure returns (VerificationKey memory vk) { if (_proofs == uint32(1)) { return getVkAggregated1(); } else if (_proofs == uint32(4)) { return getVkAggregated4(); } else if (_proofs == uint32(8)) { return getVkAggregated8(); } } 2 getVkExit() ### 区块,账户电路 #### 1 block commitment 区块电路:一批交易作用后,从上一个区块状态到新的区块状态的状态转移函数. Public inputs(电路的公开输入): - pub_data_commitment: 区块的新状态的承诺,是一个hash,包含以下内容: old_root, new_root, block_number, validator_address, pub_data_rolling_hash , timestamp, onhain_ops_offset_commitment Witness(电路的见证): 1. old_root, 2. new_root, 3. block_number, 4. validator_address, 5. pub_data, 6. pub_data_rolling_hash, 7. list of transactions, 8. state Merkle trees. - oncahin_ops_offset_commitment 是字节数组,其中一个字节对应于每个公共数据块(chunk),如果数据块是onchain操作的第一个数据块,则被设置为1,否则为0。 #### 2 账户树 主要有accout tree 和 balance tree; account tree height(24) - nonce - pubkey_hash //RollupPubkeyHash - address //EthAddress - state_tree_root balance tree height(8) 即各种token的余额 ![](https://i.imgur.com/gkeFu9U.png) 整个状态是由账户和Token信息组成的两层Merkle树。账户是以追加方式一个个添加。账户信息比较少的情况下,存在大量的空节点。 有一个主要的account tree,并且叶子账户都有一个余额树。 整个只维护状态树的root即可。 在执行完当前Operation后,当前的branch的相关信息(nonce,balance等等)都会更新。需要更新一下对应的状态根,因为下一个Operation需要在新的状态根上更新。注意,下一个Operation提供的branch witness信息是在上一个Operation更新的基础上产生的。电路只需要证明新的Root正确即可。这个也是原因,整个证明电路并没有维护整个状态树的原因。 ## 疑问 ![](https://i.imgur.com/RQQ6U5J.png) order没有tree。 更改状态都是在account tree上 ## 参考链接: - https://github.com/matter-labs/zksync/blob/master/docs/protocol.md#rollup-operation-8 - https://docs.zksync.io/dev/swaps/#atomic-swaps - https://mp.weixin.qq.com/s/lTuKbI2S1tOHeYYNm9A2dQ - https://docs.starkware.co/starkex/oracle-price-tick.html