值得信赖的区块链资讯!
L2 – 深入理解zkSync电路
zkSync的电路设计很有意思,值得好好学习。众所周知,一个区块中会打包不同的交易,如果只是针对一个个交易进行电路的证明,电路大小会变化。zkSync将交易切割成更小的“通用电路“。一个区块中包含固定的”通用电路“,间接支持多个交易。
zkSync的源代码目录下的docs/circuit.md简述了zksync的电路实现的原理以及各个操作的结构。zkSync的Layer2交易证明基于PLONK证明系统,功能模块如下:

1. 电路表示
zksync的电路由FranklinCircuit结构表示。整个电路的逻辑实现在core/circuit/src/circuit.rs。
pub struct FranklinCircuit<'a, E: RescueEngine + JubjubEngine> {
pub rescue_params: &'a <E as RescueEngine>::Params,
pub jubjub_params: &'a <E as JubjubEngine>::Params,
/// The old root of the tree
pub old_root: Option<E::Fr>,
pub initial_used_subtree_root: Option<E::Fr>,
pub block_number: Option<E::Fr>,
pub validator_address: Option<E::Fr>,
pub pub_data_commitment: Option<E::Fr>,
pub operations: Vec<Operation<E>>,
pub validator_balances: Vec<Option<E::Fr>>,
pub validator_audit_path: Vec<Option<E::Fr>>,
pub validator_account: AccountWitness<E>,
}
FranklinCircuit包括了Rescue以及Jubjub参数,老的状态根,Validator的地址/余额以及在Merkle树上的路径信息,pub data的commitment信息(区块中所有的Operation对应的pub data对应的commitment),以及各个Operation的信息。简单的说,FranklinCircuit包括了,当前状态(状态根),交易信息(pub data以及Operation)以及Validator需要收取的手续费。
Pub Data
每一笔Layer2的交易,会发布完备的信息到Layer1。这些信息就称为Pub Data。每种交易类型有不同长度。为了固定证明电路,Pub Data被切割成多个块(Chunk)。

Operation
从电路的角度,每一种交易,“分割”成多个Operation。一个区块中的交易分割成多个Operation。

在证明了这些Operation的正确性后,潜在证明了区块中包含的交易的正确性。电路中的Operation定义在core/circuit/src/operation.rs中 :
pub struct Operation<E: RescueEngine> {
pub new_root: Option<E::Fr>,
pub tx_type: Option<E::Fr>,
pub chunk: Option<E::Fr>,
pub pubdata_chunk: Option<E::Fr>,
pub signer_pub_key_packed: Vec<Option<bool>>,
pub first_sig_msg: Option<E::Fr>,
pub second_sig_msg: Option<E::Fr>,
pub third_sig_msg: Option<E::Fr>,
pub signature_data: SignatureData,
pub args: OperationArguments<E>,
pub lhs: OperationBranch<E>,
pub rhs: OperationBranch<E>,
}
new_root是新的状态根,tx_typ指交易类型,chunk是交易多个chunk的编号,pubdata_chunk是交易对应的pubdata的chunk编号。signer_pub_key_packed是交易签名的pub key。整个交易(msg)分成最大三段。每段的字节长度不超过Fr的bit个数。signature_data是签名信息。args是交易的参数信息。lhs/rhs分别指的是状态操作的分支信息。
交易的参数信息由OperationArguments数据结构表示。你可以把Operation中除了OperationArugments的部分想象成一种操作的固定信息。OperationArguments的部分是一种操作变化的部分。举个例子,Transfer的交易,Operation中的交易类型,pub data对应的chunk是固定的。而转账金额,费用,以太地址针对不同的Transfer交易而不同。
pub struct OperationArguments<E: RescueEngine> {
pub a: Option<E::Fr>,
pub b: Option<E::Fr>,
pub amount_packed: Option<E::Fr>,
pub full_amount: Option<E::Fr>,
pub fee: Option<E::Fr>,
pub new_pub_key_hash: Option<E::Fr>,
pub eth_address: Option<E::Fr>,
pub pub_nonce: Option<E::Fr>,
}
amount_packed是交易的金额,fee是交易的费用。其他也比较清晰。讲讲a,b。在每个Operation的证明电路中,提供了一个大于等于的判断。也就是说,判定a是否大于b。针对不同的交易,a和b的含义不同。以Transfer为例,a代表发送方的余额,b代表发送的金额加上费用。Transfer的证明电路证明发送方有足够的余额支付交易。以Deposit为例,a代表金额,b设置为0。Deposit的证明电路证明Deposit的交易金额必须大于0。
某个状态分支由OperationBranch数据结构表示:
pub struct OperationBranch<E: RescueEngine> {
pub address: Option<E::Fr>,
pub token: Option<E::Fr>,
pub witness: OperationBranchWitness<E>,
}
address表示zksync内部的账户编号,token是代币对应的编号,witness是分支对应的证明信息。
2.0 电路核心逻辑
熟悉Bellman或者对R1CS电路搭建有经验的小伙伴都知道,一切从synthesize函数开始:
fn synthesize<CS: ConstraintSystem<E>>(self, cs: &mut CS) -> Result<(), SynthesisError> {
在查看具体逻辑之前,必须清晰的是,电路证明的是一个Block的合法性。一个Block包括了多个交易(Tx和PriorityOp),每个交易对应的多个Operation。
申请固定变量
申请固定为0的变量。两种形式:AllocatedNum以及CircuitElement。CircuitElement是抽象的数据结构,实现电路的最基本的元素:
let zero = AllocatedNum::alloc(cs.namespace(|| "allocate element equal to zero"), || {
Ok(E::Fr::zero())
})?;
zero.assert_zero(cs.namespace(|| "enforce zero on the zero element"))?;
// we only need this for consistency of first operation
let zero_circuit_element = CircuitElement::unsafe_empty_of_some_length(zero.clone(), 256);
可以看出,CircuitElement就是AllocatedNum的封装,提供了更多变量的信息(bits信息和长度)。
pub struct CircuitElement<E: Engine> {
number: AllocatedNum<E>,
bits_le: Vec<Boolean>,
length: usize,
}
创建PreviousData
PreviousData现在主要是前一个Operation对应的各种信息数据。
struct PreviousData<E: RescueEngine> {
op_data: AllocatedOperationData<E>,
}
let mut prev = PreviousData {
op_data: AllocatedOperationData::empty_from_zero(zero.clone())?,
}
从AllocatedOperationData的定义可以清晰的看出,AllocatedOperationData包含了前一个Operation的各种信息对应的变量。
pub struct AllocatedOperationData<E: Engine> {
pub amount_packed: CircuitElement<E>,
pub fee_packed: CircuitElement<E>,
pub amount_unpacked: CircuitElement<E>,
pub full_amount: CircuitElement<E>,
pub fee: CircuitElement<E>,
pub first_sig_msg: CircuitElement<E>,
pub second_sig_msg: CircuitElement<E>,
pub third_sig_msg: CircuitElement<E>,
pub new_pubkey_hash: CircuitElement<E>,
pub eth_address: CircuitElement<E>,
pub pub_nonce: CircuitElement<E>,
pub a: CircuitElement<E>,
pub b: CircuitElement<E>,
}
创建Pub Data Commitment
区块信息对应的Pub Data从Layer2会提交到Layer1。区块证明的信息,显然要验证是否和提交的Pub Data一致。
// vector of pub_data_bits that will be aggregated during block processing
let mut block_pub_data_bits = vec![];
let public_data_commitment =
AllocatedNum::alloc(cs.namespace(|| "public_data_commitment"), || {
self.pub_data_commitment.grab()
})?;
public_data_commitment.inputize(cs.namespace(|| "inputize pub_data"))?;
public_data_commitment也是整个电路唯一的公开输入。
确认状态根
整个状态是由账户和Token信息组成的两层Merkle树。账户是以追加方式一个个添加。账户信息比较少的情况下,存在大量的空节点。
let old_root = AllocatedNum::alloc(cs.namespace(|| "old_root"), || self.old_root.grab())?;
let mut rolling_root = {
let initial_used_subtree_root =
AllocatedNum::alloc(cs.namespace(|| "initial_used_subtree_root"), || {
self.initial_used_subtree_root.grab()
})?;
let old_root_from_subroot = continue_leftmost_subroot_to_root(
cs.namespace(|| "continue initial_used_subtree root to old_root"),
&initial_used_subtree_root,
params::used_account_subtree_depth(),
params::account_tree_depth(),
self.rescue_params,
)?;
// ensure that old root contains initial_root
cs.enforce(
|| "old_root contains initial_used_subtree_root",
|lc| lc + old_root_from_subroot.get_variable(),
|lc| lc + CS::one(),
|lc| lc + old_root.get_variable(),
);
initial_used_subtree_root
};
rolling_root为包括了所有账户的最小树的树根。从rolling_root到最终的root,需要再和一些空节点计算hash。
确定Chunk的状态
next_chunk_num为下一个chunk的编号对应的变量。区块中的第一个交易的第一个Operation的第一个chunk编号为0。
let mut next_chunk_number = zero.clone();
let mut allocated_chunk_data: AllocatedChunkData<E> = AllocatedChunkData {
is_chunk_last: Boolean::constant(false),
is_chunk_first: Boolean::constant(false),
chunk_number: zero_circuit_element.get_number(),
tx_type: zero_circuit_element,
};
allocated_chunk_data表明当前的chunk的状态:是否是第一个或者最后一个状态,对应的chunk编号,以及交易类型。注意,这些都是电路的变量。
确定Pub Data是否一致
从证明电路的角度看,每个交易都划分为不同的Operation。针对一个交易的不同Operation,要证明处理的是同一个Pub Data。pubdata_holder就是实现这样的目的。因为一个区块可能包含不同的Operation,所以,pubdata_holder枚举包含所有的Operation的可能性。
let mut pubdata_holder = {
let mut data = vec![vec![]; DIFFERENT_TRANSACTIONS_TYPE_NUMBER];
data[NoopOp::OP_CODE as usize] = vec![]; // No-op allocated constant pubdata
data[DepositOp::OP_CODE as usize] = vec![zero.clone(); 2];
data[TransferOp::OP_CODE as usize] = vec![zero.clone(); 1];
data[TransferToNewOp::OP_CODE as usize] = vec![zero.clone(); 2];
data[WithdrawOp::OP_CODE as usize] = vec![zero.clone(); 2];
data[FullExitOp::OP_CODE as usize] = vec![zero.clone(); 2];
data[ChangePubKeyOp::OP_CODE as usize] = vec![zero.clone(); 2];
// this operation is disabled for now
// data[CloseOp::OP_CODE as usize] = vec![];
data
};
初始化Fee对应变量
在一个区块中的交易,可能转账或者提取任何Token。所以,一个区块对应的Fees是一个数组。Fee初始都为0。
let mut fees = vec![];
let fees_len = params::number_of_processable_tokens();
for _ in 0..fees_len {
fees.push(zero_circuit_element.get_number());
}
Operation处理
整个证明电路包括了一个区块中的所有Operation的证明。
// Main cycle that processes operations:
for (i, operation) in self.operations.iter().enumerate() {
}
每一个Operation的处理都可以分成几个小步骤:
1. 验证chunk编号
验证当前的chunk是不是和next_chunk_number一致。并且创建更多变量,表示当前的状态:交易类型,是不是第一个chunk,是不是最后一个chunk,当前的chunk编号。
let (next_chunk, chunk_data) = self.verify_correct_chunking(
&operation,
&next_chunk_number,
cs.namespace(|| "verify_correct_chunking"),
)?;

2. 收集pub data
将每个Operation对应的chunk data收集到block pub data中。
let operation_pub_data_chunk = CircuitElement::from_fe_with_known_length(
cs.namespace(|| "operation_pub_data_chunk"),
|| operation.clone().pubdata_chunk.grab(),
params::CHUNK_BIT_WIDTH,
)?;
block_pub_data_bits.extend(operation_pub_data_chunk.get_bits_le());
3. 分支选择
逻辑上,每个交易会改变一些branch,所谓的branch,就是状态树上的分支(账户和Token)。
let lhs =
AllocatedOperationBranch::from_witness(cs.namespace(|| "lhs"), &operation.lhs)?;
let rhs =
AllocatedOperationBranch::from_witness(cs.namespace(|| "rhs"), &operation.rhs)?;
let mut current_branch = self.select_branch(
cs.namespace(|| "select appropriate branch"),
&lhs,
&rhs,
operation,
&allocated_chunk_data,
)?;
目前的交易类型,最多改动两个分支。也就是Operation里面定义的lhs和rhs(左分支和右分支)。针对每个Operation,选定一个branch(需要在Operation后进行一定的更新,Nonce,Balance等等)。当然,对于一个交易的所有Operation,要能覆盖所有的branch。相关的逻辑由select_branch确定。
如果是Deposit交易,永远选择lhs。如果是其他交易,第一个选择lhs,其他选择rhs。
4. 状态根检查
在选定branch后,需要确定当前状态和之前计算的rolling是否一致。
// calculate root for given account data
let (state_root, is_account_empty, _subtree_root) = check_account_data(
cs.namespace(|| "calculate account root"),
¤t_branch,
params::used_account_subtree_depth(),
self.rescue_params,
)?;
// ensure root hash of state before applying operation is correct
cs.enforce(
|| "root state before applying operation is valid",
|lc| lc + state_root.get_variable(),
|lc| lc + CS::one(),
|lc| lc + rolling_root.get_variable(),
);
5. 执行Operation
execute_op执行某个具体的Operation。逻辑上,execute_op电路中包括了所有的可能的Operation的操作。只是在执行某个具体的Operation,只有其中一个有效。
self.execute_op(
cs.namespace(|| "execute_op"),
&mut current_branch,
&lhs,
&rhs,
&operation,
&allocated_chunk_data,
&is_account_empty,
&operation_pub_data_chunk.get_number(),
// &subtree_root, // Close disable
&mut last_token_id,
&mut fees,
&mut prev,
&mut pubdata_holder,
&zero,
)?;
execute_op检查如下的条件是否满足:
1/ 左右的branch的token,是否一致?
2/ Op Data和Prev是否一致?
3/ 交易签名是否正确?
4/ a是否满足大于等于b?(a/b,请查看Operation)
5/ 是否是支持的交易中的一种?
6/ 更新Fee
因为一个Opeartion的电路需要逻辑上支持不同交易。不同交易不一样的逻辑由单独的电路处理。目前支持支持7种操作,以transer为例,理解一下具体的业务电路的实现:
fn transfer<CS: ConstraintSystem<E>>(
&self,
mut cs: CS,
cur: &mut AllocatedOperationBranch<E>,
lhs: &AllocatedOperationBranch<E>,
rhs: &AllocatedOperationBranch<E>,
chunk_data: &AllocatedChunkData<E>,
is_a_geq_b: &Boolean,
is_account_empty: &Boolean,
op_data: &AllocatedOperationData<E>,
signer_key: &AllocatedSignerPubkey<E>,
ext_pubdata_chunk: &AllocatedNum<E>,
is_sig_verified: &Boolean,
pubdata_holder: &mut Vec<AllocatedNum<E>>,
) -> Result<Boolean, SynthesisError> {
1/ 确保pub data一致
2/ 确保是Transer类型
3/ 确保a/b的意义一致(a代表余额,b代表转账金额加上手续费)
4/ 两个branch的信息是否有效?
5/ 更新当前的branch信息
6. 确认并更新状态根
在执行完当前Operation后,当前的branch的相关信息(nonce,balance等等)都会更新。需要更新一下对应的状态根,因为下一个Operation需要在新的状态根上更新。注意,下一个Operation提供的branch witness信息是在上一个Operation更新的基础上产生的。电路只需要证明新的Root正确即可。这个也是原因,整个证明电路并没有维护整个状态树的原因。
let (new_state_root, _, _) = check_account_data(
cs.namespace(|| "calculate new account root"),
¤t_branch,
params::used_account_subtree_depth(),
self.rescue_params,
)?;
rolling_root = new_state_root;
查看Chunk是否完整
在Block中的所有Operation执行完成后,is_chunk_last应为true。也就是说,Block中的最后一个交易的Operation是完整的。
cs.enforce(
|| "ensure last chunk of the block is a last chunk of corresponding transaction",
|_| {
allocated_chunk_data
.is_chunk_last
.lc(CS::one(), E::Fr::one())
},
|lc| lc + CS::one(),
|lc| lc + CS::one(),
);
计算交易费后的状态
在支付交易费给Validator后,重新计算Validator的Balance树的树根,以及新的状态根。
//apply fees to operator balances
for i in 0..fees_len {
validator_balances_processable_tokens[i] = allocate_sum(
cs.namespace(|| format!("validator balance number i {}", i)),
&validator_balances_processable_tokens[i],
&fees[i],
)?;
}
// calculate operator's balance_tree root hash from whole tree representation
let new_operator_balance_root = calculate_balances_root_from_left_tree_values(
cs.namespace(|| "calculate_root_from_full_representation_fees after"),
&validator_balances_processable_tokens,
params::balance_tree_depth(),
self.rescue_params,
)?;
let mut operator_account_data = vec![];
let new_operator_state_root = {
let balance_root = CircuitElement::from_number(
cs.namespace(|| "new_operator_balance_root_ce"),
new_operator_balance_root,
)?;
calc_account_state_tree_root(
cs.namespace(|| "new_operator_state_root"),
&balance_root,
&self.rescue_params,
)?
};
operator_account_data.extend(validator_account.nonce.get_bits_le());
operator_account_data.extend(validator_account.pub_key_hash.get_bits_le());
operator_account_data.extend(validator_account.address.get_bits_le());
operator_account_data
.extend(new_operator_state_root.into_padded_le_bits(FR_BIT_WIDTH_PADDED));
let root_from_operator_after_fees = allocate_merkle_root(
cs.namespace(|| "root from operator_account after fees"),
&operator_account_data,
&validator_address_bits,
&validator_audit_path,
params::used_account_subtree_depth(),
self.rescue_params,
)?;
let final_root = continue_leftmost_subroot_to_root(
cs.namespace(|| "continue subroot to root"),
&root_from_operator_after_fees,
params::used_account_subtree_depth(),
params::account_tree_depth(),
self.rescue_params,
)?;
验证Pub Data Commitment
最后检查pub data commitment是否正确,计算方式如下:

至此,所有电路处理的逻辑就结束了。
总结:
zkSync采用PlonK零知识证明系统。在电路设计上,非常巧妙的将交易分割成一个个小的通用处理单元(Operation)。一个Operation对应的证明电路逻辑支持所有可能交易的Operation逻辑。多个有关联的Operation电路组成交易电路。多个交易的电路再组合成区块电路。从而,在固定大小的区块中也能包含不同组合的交易。
来源:Star Li
比推快讯
更多 >>- 金融巨头Apex Group试点特朗普旗下WLFI稳定币,用于代币化基金业务
- Coinbase 通过 Morpho 将 XRP、狗狗币、Cardano和莱特币添加为链上贷款抵押品
- OpenAI推出智能合约测试框架EVMbench
- 数据:过去 24 小时全网爆仓 2.27 亿美元,多单爆仓 1.7 亿美元,空单爆仓 5,669.64 万美元
- 美联储纪要显示官员对通胀忧虑上升,劳动力市场风险减退
- 美联储传声筒:美联储会议纪要显示降息意愿寥寥 多位官员支持“双向”描述
- 美联储会议纪要:几位官员支持双向利率决策描述,为通胀高企时加息留出空间
- 美联储会议纪要披露“汇率检查”,美元显著贬值
- 美联储会议纪要:若通胀如预期下降,进一步降息更有可能
- 美联储会议纪要,通胀向 2% 目标进程或更缓慢
- 美联储会议纪要显示,与会者支持双向表述利率决议
- 美联储会议纪要显示经济展望强劲,通胀略高于预期
- 美联储会议纪要显示少数与会者倾向于 1 月降息
- 美联储会议纪要:几乎所有与会者都支持在 1 月份暂停利率行动
- 美联储会议纪要:与会者预计通胀将向 2%的目标回落,但回落的速度和时间仍不确定
- 数据:若 ETH 突破 2,064 美元,主流 CEX 累计空单清算强度将达 8.49 亿美元
- 欧元兑美元日内跌超 0.5%,现报 1.1795
- 美联储逆回购操作接纳 85.6 亿美元资金
- 美元指数 DXY 日内涨幅扩大至 0.5%,现报 97.61
- 城堡证券:软件股惨遭抛售之际,散户正以“前所未见”速度抄底
- Base网络进行重大技术转向,逐步脱离Optimism的OP Stack
- 高盛 CEO:持有非常少量比特币,正密切关注比特币的发展
- 某鲸鱼做多 4247 万枚 WLFI 已扭亏为盈,此前一度浮亏超 100 万美元
- WLFI 突破 0.12 美元,24 小时涨幅 22.39%
- Whale Alert 创始人:BTC 潜在盈利水平回落至 2023 年底,或临近三年盈利周期拐点
- 跨链协议 Owlto 上线隐私跨链模式
- Kraken 收购代币管理公司 Magna,为其 IPO 做准备
- 纳指涨幅扩大至 1%,英伟达涨 2.54%
- BitMine 于 2 小时前再次增持 20,000 枚 ETH
- 美国银行披露 2025 年 Q4 持有 3,162,085 股 BMNR,持股增幅达 1668%
- BTC 跌破 67000 USDT,24H 跌幅 0.02%
- Visa 授予荷兰支付公司 Quantoz 主要会员资格,支持发行稳定币借记卡
- 数据:BTC 跌破 67000 美元
- 美股开盘加密概念股普跌,GEMI 跌 3.26%
- 美股高开,道指涨 0.16%,英伟达涨 1.8%
- 标普 500、纳斯达克和道琼斯指数开盘均上涨
- 美股开盘加密板块涨跌互现,Coinbase (COIN) 小幅上涨 0.13%
- MYX 完成战略融资,V2 版本即将发布
- XRP Ledger 推出为受监管机构设计的会员专属 DEX
- Glassnode:市场获利了结正在降温,但尚未进入恐慌性抛售区间
- 现货白银上涨触及 77 美元/盎司,日内涨 4.75%
- K33:比特币进入“熊市后期区域”,市场信号与 2022 年底部相似
- 美元兑日元突破 154,日内涨幅达 0.48%
- 分析:山寨币成交量减少 50%,资金回流至比特币
- Battery Ventures 逆势完成 32.5 亿美元新基金募集,四个月内完成交割
- 链上 ASTER 最大多头neoyokio.eth持续滚仓,持仓规模已达 1490 万美元
- 比特币财库公司 Hyperscale Data 启动战略白银储备计划拟购买 10 万盎司白银
- Hyperliquid 成立游说组织Hyperliquid Policy Center,将以 2800 万美元 HYPE 作为启动资金
- Hyperliquid 推出由知名加密律师领导的 DeFi 政策中心
- 超过 50%的美国投资者担心因今年实施新规而面临国税局处罚
比推专栏
更多 >>- Happy new year【Horse success】|0213Asian
- Was it finished?|0206 Asian
- 围猎以太坊多头:「巨鲸」们暴亏 70 亿美元,正被集体围观
- Challenge,risk And chances|0130 Asian
- Meta 豪赌 AI:砸钱 1350 亿美元,2026 的扎克伯格,值得相信么?
- Variables: Terrible snowstorm|0128 Asian
- 英特尔「生死线」时刻:在 ICU 门前,陈立武如何清算遗产并开启自救?
- 從1月13號到今天,提前到5100|0126Asian
- You Should work HARDER in 2026|0120 Asian
- 硅谷最聪明那群人的「终极推演」:2026,我们应该「All-In」什么?
观点
比推热门文章
- 金融巨头Apex Group试点特朗普旗下WLFI稳定币,用于代币化基金业务
- Coinbase 通过 Morpho 将 XRP、狗狗币、Cardano和莱特币添加为链上贷款抵押品
- OpenAI推出智能合约测试框架EVMbench
- 数据:过去 24 小时全网爆仓 2.27 亿美元,多单爆仓 1.7 亿美元,空单爆仓 5,669.64 万美元
- 美联储纪要显示官员对通胀忧虑上升,劳动力市场风险减退
- 美联储传声筒:美联储会议纪要显示降息意愿寥寥 多位官员支持“双向”描述
- 美联储会议纪要:几位官员支持双向利率决策描述,为通胀高企时加息留出空间
- 美联储会议纪要披露“汇率检查”,美元显著贬值
- 美联储会议纪要:若通胀如预期下降,进一步降息更有可能
- Delphi Digital:代币化时代已来
比推 APP



